Skip to content

Commit 2f6c4d4

Browse files
Merge branch '7.4' into 8.0
* 7.4: (31 commits) [Validator] Update Romanian translations fix tests [JsonStreamer] Fix decoding iterable lists [String][Inflector] Fix edge cases [Serializer][Validator] Add JSON schema for validating and autocompleting YAML config files [DependencyInjection] Allow adding resource tags using any config formats Fix merge Add missing Sweego Mailer Bridge webhook events [Security] Fix attribute-based chained user providers [Intl] Fix Intl::getIcuStubVersion() [Intl] Add methods to filter currencies more precisely [Notifier] Add tests for option classes Sync intl scripts [Intl] Add metadata about currencies' validtity dates Bump Symfony version to 7.3.4 Update VERSION for 7.3.3 Update CHANGELOG for 7.3.3 Bump Symfony version to 6.4.26 Update VERSION for 6.4.25 Update CONTRIBUTORS for 6.4.25 ...
2 parents c84e8ca + b3b8971 commit 2f6c4d4

File tree

5 files changed

+324
-1
lines changed

5 files changed

+324
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
---
1313

1414
* Add `input=date_point` to `DateTimeType`, `DateType` and `TimeType`
15+
* Add support for guessing form type of enum properties
1516

1617
7.3
1718
---

EnumFormTypeGuesser.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form;
13+
14+
use Symfony\Component\Form\Extension\Core\Type\EnumType;
15+
use Symfony\Component\Form\Guess\Guess;
16+
use Symfony\Component\Form\Guess\TypeGuess;
17+
use Symfony\Component\Form\Guess\ValueGuess;
18+
19+
final class EnumFormTypeGuesser implements FormTypeGuesserInterface
20+
{
21+
/**
22+
* @var array<string, array<string, string|false>>
23+
*/
24+
private array $cache = [];
25+
26+
public function guessType(string $class, string $property): ?TypeGuess
27+
{
28+
if (!($enum = $this->getPropertyType($class, $property))) {
29+
return null;
30+
}
31+
32+
return new TypeGuess(EnumType::class, ['class' => ltrim($enum, '?')], Guess::HIGH_CONFIDENCE);
33+
}
34+
35+
public function guessRequired(string $class, string $property): ?ValueGuess
36+
{
37+
if (!($enum = $this->getPropertyType($class, $property))) {
38+
return null;
39+
}
40+
41+
return new ValueGuess('?' !== $enum[0], Guess::HIGH_CONFIDENCE);
42+
}
43+
44+
public function guessMaxLength(string $class, string $property): ?ValueGuess
45+
{
46+
return null;
47+
}
48+
49+
public function guessPattern(string $class, string $property): ?ValueGuess
50+
{
51+
return null;
52+
}
53+
54+
private function getPropertyType(string $class, string $property): string|false
55+
{
56+
if (isset($this->cache[$class][$property])) {
57+
return $this->cache[$class][$property];
58+
}
59+
60+
try {
61+
$propertyReflection = new \ReflectionProperty($class, $property);
62+
} catch (\ReflectionException) {
63+
return $this->cache[$class][$property] = false;
64+
}
65+
66+
$type = $propertyReflection->getType();
67+
if (!$type instanceof \ReflectionNamedType || !enum_exists($type->getName())) {
68+
$enum = false;
69+
} else {
70+
$enum = $type->getName();
71+
if ($type->allowsNull()) {
72+
$enum = '?'.$enum;
73+
}
74+
}
75+
76+
return $this->cache[$class][$property] = $enum;
77+
}
78+
}

Tests/DependencyInjection/FormPassTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function testAddTaggedTypes()
6161
$extDefinition = $container->getDefinition('form.extension');
6262

6363
$locator = $extDefinition->getArgument(0);
64-
$this->assertTrue(!$locator->isPublic() || $locator->isPrivate());
64+
$this->assertTrue($locator->isPrivate());
6565
$this->assertEquals(
6666
(new Definition(ServiceLocator::class, [[
6767
__CLASS__.'_Type1' => new ServiceClosureArgument(new Reference('my.type1')),

Tests/EnumFormTypeGuesserTest.php

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests;
13+
14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Form\EnumFormTypeGuesser;
17+
use Symfony\Component\Form\Extension\Core\Type\EnumType as FormEnumType;
18+
use Symfony\Component\Form\Guess\Guess;
19+
use Symfony\Component\Form\Guess\TypeGuess;
20+
use Symfony\Component\Form\Guess\ValueGuess;
21+
use Symfony\Component\Form\Tests\Fixtures\BackedEnumFormTypeGuesserCaseEnum;
22+
use Symfony\Component\Form\Tests\Fixtures\EnumFormTypeGuesserCase;
23+
use Symfony\Component\Form\Tests\Fixtures\EnumFormTypeGuesserCaseEnum;
24+
25+
class EnumFormTypeGuesserTest extends TestCase
26+
{
27+
#[DataProvider('provideGuessTypeCases')]
28+
public function testGuessType(?TypeGuess $expectedTypeGuess, string $class, string $property)
29+
{
30+
$typeGuesser = new EnumFormTypeGuesser();
31+
32+
$typeGuess = $typeGuesser->guessType($class, $property);
33+
34+
self::assertEquals($expectedTypeGuess, $typeGuess);
35+
}
36+
37+
#[DataProvider('provideGuessRequiredCases')]
38+
public function testGuessRequired(?ValueGuess $expectedValueGuess, string $class, string $property)
39+
{
40+
$typeGuesser = new EnumFormTypeGuesser();
41+
42+
$valueGuess = $typeGuesser->guessRequired($class, $property);
43+
44+
self::assertEquals($expectedValueGuess, $valueGuess);
45+
}
46+
47+
public static function provideGuessTypeCases(): iterable
48+
{
49+
yield 'Undefined class' => [
50+
null,
51+
'UndefinedClass',
52+
'undefinedProperty',
53+
];
54+
55+
yield 'Undefined property' => [
56+
null,
57+
EnumFormTypeGuesserCase::class,
58+
'undefinedProperty',
59+
];
60+
61+
yield 'Undefined enum' => [
62+
null,
63+
EnumFormTypeGuesserCase::class,
64+
'undefinedEnum',
65+
];
66+
67+
yield 'Non-enum property' => [
68+
null,
69+
EnumFormTypeGuesserCase::class,
70+
'string',
71+
];
72+
73+
yield 'Enum property' => [
74+
new TypeGuess(
75+
FormEnumType::class,
76+
[
77+
'class' => EnumFormTypeGuesserCaseEnum::class,
78+
],
79+
Guess::HIGH_CONFIDENCE,
80+
),
81+
EnumFormTypeGuesserCase::class,
82+
'enum',
83+
];
84+
85+
yield 'Nullable enum property' => [
86+
new TypeGuess(
87+
FormEnumType::class,
88+
[
89+
'class' => EnumFormTypeGuesserCaseEnum::class,
90+
],
91+
Guess::HIGH_CONFIDENCE,
92+
),
93+
EnumFormTypeGuesserCase::class,
94+
'nullableEnum',
95+
];
96+
97+
yield 'Backed enum property' => [
98+
new TypeGuess(
99+
FormEnumType::class,
100+
[
101+
'class' => BackedEnumFormTypeGuesserCaseEnum::class,
102+
],
103+
Guess::HIGH_CONFIDENCE,
104+
),
105+
EnumFormTypeGuesserCase::class,
106+
'backedEnum',
107+
];
108+
109+
yield 'Nullable backed enum property' => [
110+
new TypeGuess(
111+
FormEnumType::class,
112+
[
113+
'class' => BackedEnumFormTypeGuesserCaseEnum::class,
114+
],
115+
Guess::HIGH_CONFIDENCE,
116+
),
117+
EnumFormTypeGuesserCase::class,
118+
'nullableBackedEnum',
119+
];
120+
121+
yield 'Enum union property' => [
122+
null,
123+
EnumFormTypeGuesserCase::class,
124+
'enumUnion',
125+
];
126+
127+
yield 'Enum intersection property' => [
128+
null,
129+
EnumFormTypeGuesserCase::class,
130+
'enumIntersection',
131+
];
132+
}
133+
134+
public static function provideGuessRequiredCases(): iterable
135+
{
136+
yield 'Unknown class' => [
137+
null,
138+
'UndefinedClass',
139+
'undefinedProperty',
140+
];
141+
142+
yield 'Unknown property' => [
143+
null,
144+
EnumFormTypeGuesserCase::class,
145+
'undefinedProperty',
146+
];
147+
148+
yield 'Undefined enum' => [
149+
null,
150+
EnumFormTypeGuesserCase::class,
151+
'undefinedEnum',
152+
];
153+
154+
yield 'Non-enum property' => [
155+
null,
156+
EnumFormTypeGuesserCase::class,
157+
'string',
158+
];
159+
160+
yield 'Enum property' => [
161+
new ValueGuess(
162+
true,
163+
Guess::HIGH_CONFIDENCE,
164+
),
165+
EnumFormTypeGuesserCase::class,
166+
'enum',
167+
];
168+
169+
yield 'Nullable enum property' => [
170+
new ValueGuess(
171+
false,
172+
Guess::HIGH_CONFIDENCE,
173+
),
174+
EnumFormTypeGuesserCase::class,
175+
'nullableEnum',
176+
];
177+
178+
yield 'Backed enum property' => [
179+
new ValueGuess(
180+
true,
181+
Guess::HIGH_CONFIDENCE,
182+
),
183+
EnumFormTypeGuesserCase::class,
184+
'backedEnum',
185+
];
186+
187+
yield 'Nullable backed enum property' => [
188+
new ValueGuess(
189+
false,
190+
Guess::HIGH_CONFIDENCE,
191+
),
192+
EnumFormTypeGuesserCase::class,
193+
'nullableBackedEnum',
194+
];
195+
196+
yield 'Enum union property' => [
197+
null,
198+
EnumFormTypeGuesserCase::class,
199+
'enumUnion',
200+
];
201+
202+
yield 'Enum intersection property' => [
203+
null,
204+
EnumFormTypeGuesserCase::class,
205+
'enumIntersection',
206+
];
207+
}
208+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Fixtures;
13+
14+
class EnumFormTypeGuesserCase
15+
{
16+
public string $string;
17+
public UndefinedEnum $undefinedEnum;
18+
public EnumFormTypeGuesserCaseEnum $enum;
19+
public ?EnumFormTypeGuesserCaseEnum $nullableEnum;
20+
public BackedEnumFormTypeGuesserCaseEnum $backedEnum;
21+
public ?BackedEnumFormTypeGuesserCaseEnum $nullableBackedEnum;
22+
public EnumFormTypeGuesserCaseEnum|BackedEnumFormTypeGuesserCaseEnum $enumUnion;
23+
public EnumFormTypeGuesserCaseEnum&BackedEnumFormTypeGuesserCaseEnum $enumIntersection;
24+
}
25+
26+
enum EnumFormTypeGuesserCaseEnum
27+
{
28+
case Foo;
29+
case Bar;
30+
}
31+
32+
enum BackedEnumFormTypeGuesserCaseEnum: string
33+
{
34+
case Foo = 'foo';
35+
case Bar = 'bar';
36+
}

0 commit comments

Comments
 (0)