Skip to content

Commit 80d176c

Browse files
committed
[Form] Add form type guesser for EnumType
1 parent 39c5025 commit 80d176c

File tree

5 files changed

+332
-0
lines changed

5 files changed

+332
-0
lines changed

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: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
/**
20+
* @author Matthias Schmidt <[email protected]>
21+
*/
22+
final class EnumFormTypeGuesser implements FormTypeGuesserInterface
23+
{
24+
private array $cache = [];
25+
26+
public function guessType(string $class, string $property): ?TypeGuess
27+
{
28+
if (null === ($propertyType = $this->getPropertyType($class, $property))) {
29+
return null;
30+
}
31+
32+
return new TypeGuess(FormEnumType::class, ['class' => $propertyType->getName()], Guess::HIGH_CONFIDENCE);
33+
}
34+
35+
public function guessRequired(string $class, string $property): ?ValueGuess
36+
{
37+
if (null === $propertyType = $this->getPropertyType($class, $property)) {
38+
return null;
39+
}
40+
41+
return new ValueGuess(!$propertyType->allowsNull(), 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): ?\ReflectionNamedType
55+
{
56+
if (isset($this->cache[$class]) && \array_key_exists($property, $this->cache[$class])) {
57+
return $this->cache[$class][$property];
58+
}
59+
60+
if (!isset($this->cache[$class])) {
61+
$this->cache[$class] = [];
62+
}
63+
64+
try {
65+
$classReflection = new \ReflectionClass($class);
66+
$propertyReflection = $classReflection->getProperty($property);
67+
} catch (\ReflectionException) {
68+
return $this->cache[$class][$property] = null;
69+
}
70+
71+
if (!$propertyReflection->getType() instanceof \ReflectionNamedType || !enum_exists($propertyReflection->getType()->getName())) {
72+
return $this->cache[$class][$property] = null;
73+
}
74+
75+
return $this->cache[$class][$property] = $propertyReflection->getType();
76+
}
77+
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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\TestCase;
15+
use Symfony\Component\Form\EnumFormTypeGuesser;
16+
use Symfony\Component\Form\Extension\Core\Type\EnumType as FormEnumType;
17+
use Symfony\Component\Form\Guess\Guess;
18+
use Symfony\Component\Form\Guess\TypeGuess;
19+
use Symfony\Component\Form\Guess\ValueGuess;
20+
use Symfony\Component\Form\Tests\Fixtures\BackedEnumFormTypeGuesserCaseEnum;
21+
use Symfony\Component\Form\Tests\Fixtures\EnumFormTypeGuesserCase;
22+
use Symfony\Component\Form\Tests\Fixtures\EnumFormTypeGuesserCaseEnum;
23+
24+
/**
25+
* @author Matthias Schmidt <[email protected]>
26+
*/
27+
final class EnumFormTypeGuesserTest extends TestCase
28+
{
29+
/**
30+
* @dataProvider provideGuessTypeCases
31+
*/
32+
public function testGuessType(?TypeGuess $expectedTypeGuess, string $class, string $property)
33+
{
34+
$typeGuesser = new EnumFormTypeGuesser();
35+
36+
$typeGuess = $typeGuesser->guessType($class, $property);
37+
38+
self::assertEquals($expectedTypeGuess, $typeGuess);
39+
}
40+
41+
/**
42+
* @dataProvider provideGuessRequiredCases
43+
*/
44+
public function testGuessRequired(?ValueGuess $expectedValueGuess, string $class, string $property)
45+
{
46+
$typeGuesser = new EnumFormTypeGuesser();
47+
48+
$valueGuess = $typeGuesser->guessRequired($class, $property);
49+
50+
self::assertEquals($expectedValueGuess, $valueGuess);
51+
}
52+
53+
public static function provideGuessTypeCases(): iterable
54+
{
55+
yield 'Undefined class' => [
56+
null,
57+
'UndefinedClass',
58+
'undefinedProperty',
59+
];
60+
61+
yield 'Undefined property' => [
62+
null,
63+
EnumFormTypeGuesserCase::class,
64+
'undefinedProperty',
65+
];
66+
67+
yield 'Undefined enum' => [
68+
null,
69+
EnumFormTypeGuesserCase::class,
70+
'undefinedEnum',
71+
];
72+
73+
yield 'Non-enum property' => [
74+
null,
75+
EnumFormTypeGuesserCase::class,
76+
'string',
77+
];
78+
79+
yield 'Enum property' => [
80+
new TypeGuess(
81+
FormEnumType::class,
82+
[
83+
'class' => EnumFormTypeGuesserCaseEnum::class,
84+
],
85+
Guess::HIGH_CONFIDENCE,
86+
),
87+
EnumFormTypeGuesserCase::class,
88+
'enum',
89+
];
90+
91+
yield 'Nullable enum property' => [
92+
new TypeGuess(
93+
FormEnumType::class,
94+
[
95+
'class' => EnumFormTypeGuesserCaseEnum::class,
96+
],
97+
Guess::HIGH_CONFIDENCE,
98+
),
99+
EnumFormTypeGuesserCase::class,
100+
'nullableEnum',
101+
];
102+
103+
yield 'Backed enum property' => [
104+
new TypeGuess(
105+
FormEnumType::class,
106+
[
107+
'class' => BackedEnumFormTypeGuesserCaseEnum::class,
108+
],
109+
Guess::HIGH_CONFIDENCE,
110+
),
111+
EnumFormTypeGuesserCase::class,
112+
'backedEnum',
113+
];
114+
115+
yield 'Nullable backed enum property' => [
116+
new TypeGuess(
117+
FormEnumType::class,
118+
[
119+
'class' => BackedEnumFormTypeGuesserCaseEnum::class,
120+
],
121+
Guess::HIGH_CONFIDENCE,
122+
),
123+
EnumFormTypeGuesserCase::class,
124+
'nullableBackedEnum',
125+
];
126+
127+
yield 'Enum union property' => [
128+
null,
129+
EnumFormTypeGuesserCase::class,
130+
'enumUnion',
131+
];
132+
133+
yield 'Enum intersection property' => [
134+
null,
135+
EnumFormTypeGuesserCase::class,
136+
'enumIntersection',
137+
];
138+
}
139+
140+
public static function provideGuessRequiredCases(): iterable
141+
{
142+
yield 'Unknown class' => [
143+
null,
144+
'UndefinedClass',
145+
'undefinedProperty',
146+
];
147+
148+
yield 'Unknown property' => [
149+
null,
150+
EnumFormTypeGuesserCase::class,
151+
'undefinedProperty',
152+
];
153+
154+
yield 'Undefined enum' => [
155+
null,
156+
EnumFormTypeGuesserCase::class,
157+
'undefinedEnum',
158+
];
159+
160+
yield 'Non-enum property' => [
161+
null,
162+
EnumFormTypeGuesserCase::class,
163+
'string',
164+
];
165+
166+
yield 'Enum property' => [
167+
new ValueGuess(
168+
true,
169+
Guess::HIGH_CONFIDENCE,
170+
),
171+
EnumFormTypeGuesserCase::class,
172+
'enum',
173+
];
174+
175+
yield 'Nullable enum property' => [
176+
new ValueGuess(
177+
false,
178+
Guess::HIGH_CONFIDENCE,
179+
),
180+
EnumFormTypeGuesserCase::class,
181+
'nullableEnum',
182+
];
183+
184+
yield 'Backed enum property' => [
185+
new ValueGuess(
186+
true,
187+
Guess::HIGH_CONFIDENCE,
188+
),
189+
EnumFormTypeGuesserCase::class,
190+
'backedEnum',
191+
];
192+
193+
yield 'Nullable backed enum property' => [
194+
new ValueGuess(
195+
false,
196+
Guess::HIGH_CONFIDENCE,
197+
),
198+
EnumFormTypeGuesserCase::class,
199+
'nullableBackedEnum',
200+
];
201+
202+
yield 'Enum union property' => [
203+
null,
204+
EnumFormTypeGuesserCase::class,
205+
'enumUnion',
206+
];
207+
208+
yield 'Enum intersection property' => [
209+
null,
210+
EnumFormTypeGuesserCase::class,
211+
'enumIntersection',
212+
];
213+
}
214+
}
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)