Skip to content

Commit a5eebb2

Browse files
committed
[Form] Add form type guesser for EnumType
1 parent f49dbb5 commit a5eebb2

File tree

6 files changed

+328
-0
lines changed

6 files changed

+328
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
use Symfony\Component\Filesystem\Filesystem;
8484
use Symfony\Component\Finder\Finder;
8585
use Symfony\Component\Finder\Glob;
86+
use Symfony\Component\Form\EnumFormTypeGuesser;
8687
use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension;
8788
use Symfony\Component\Form\Form;
8889
use Symfony\Component\Form\FormTypeExtensionInterface;
@@ -564,6 +565,10 @@ public function load(array $configs, ContainerBuilder $container): void
564565
if (!$this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer']) || !class_exists(TextTypeHtmlSanitizerExtension::class)) {
565566
$container->removeDefinition('form.type_extension.form.html_sanitizer');
566567
}
568+
569+
if (!class_exists(EnumFormTypeGuesser::class)) {
570+
$container->removeDefinition('form.type_guesser.enum_type');
571+
}
567572
} else {
568573
$container->removeDefinition('console.command.form_debug');
569574
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/form.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
1515
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
1616
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
17+
use Symfony\Component\Form\EnumFormTypeGuesser;
1718
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
1819
use Symfony\Component\Form\Extension\Core\Type\ColorType;
1920
use Symfony\Component\Form\Extension\Core\Type\FileType;
@@ -76,6 +77,9 @@
7677
->args([service('validator.mapping.class_metadata_factory')])
7778
->tag('form.type_guesser')
7879

80+
->set('form.type_guesser.enum_type', EnumFormTypeGuesser::class)
81+
->tag('form.type_guesser')
82+
7983
->alias('form.property_accessor', 'property_accessor')
8084

8185
->set('form.choice_list_factory.default', DefaultChoiceListFactory::class)

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add `input=date_point` to `DateTimeType`, `DateType` and `TimeType`
8+
* Add support for guessing form type of enum properties
89

910
7.3
1011
---
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 as FormEnumType;
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+
private array $cache = [];
22+
23+
public function guessType(string $class, string $property): ?TypeGuess
24+
{
25+
if (null === ($propertyType = $this->getPropertyType($class, $property))) {
26+
return null;
27+
}
28+
29+
return new TypeGuess(FormEnumType::class, ['class' => $propertyType->getName()], Guess::HIGH_CONFIDENCE);
30+
}
31+
32+
public function guessRequired(string $class, string $property): ?ValueGuess
33+
{
34+
if (null === $propertyType = $this->getPropertyType($class, $property)) {
35+
return null;
36+
}
37+
38+
return new ValueGuess(!$propertyType->allowsNull(), Guess::HIGH_CONFIDENCE);
39+
}
40+
41+
public function guessMaxLength(string $class, string $property): ?ValueGuess
42+
{
43+
return null;
44+
}
45+
46+
public function guessPattern(string $class, string $property): ?ValueGuess
47+
{
48+
return null;
49+
}
50+
51+
private function getPropertyType(string $class, string $property): ?\ReflectionNamedType
52+
{
53+
if (isset($this->cache[$class]) && \array_key_exists($property, $this->cache[$class])) {
54+
return $this->cache[$class][$property];
55+
}
56+
57+
if (!isset($this->cache[$class])) {
58+
$this->cache[$class] = [];
59+
}
60+
61+
try {
62+
$classReflection = new \ReflectionClass($class);
63+
$propertyReflection = $classReflection->getProperty($property);
64+
} catch (\ReflectionException) {
65+
return $this->cache[$class][$property] = null;
66+
}
67+
68+
if (!$propertyReflection->getType() instanceof \ReflectionNamedType || !enum_exists($propertyReflection->getType()->getName())) {
69+
return $this->cache[$class][$property] = null;
70+
}
71+
72+
return $this->cache[$class][$property] = $propertyReflection->getType();
73+
}
74+
}
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+
final 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+
final 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)