diff --git a/README.md b/README.md index 8808718..fad72eb 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,7 @@ See example Symfony project in [integration test suite](tests/Integration). - Creating [enums](docs/creating-enum.md) - Creating [translated enums](docs/creating-translated-enum.md) +- Integration with [PHP native enum](docs/native-enum-integration.md) - Integration with [myclabs/php-enum](docs/myclabs-enum-integration.md) - Migrating [from standard Symfony](docs/migrating-from-symfony-standard.md) - Integration with [SonataAdminBundle](docs/sonata-admin-integration.md) diff --git a/docs/myclabs-enum-integration.md b/docs/myclabs-enum-integration.md index b2cae2d..f5d4a82 100644 --- a/docs/myclabs-enum-integration.md +++ b/docs/myclabs-enum-integration.md @@ -70,3 +70,40 @@ class StatusEnum extends MyCLabsTranslatedEnum } } ``` + +## Submitting values through a form + +Because values of enum like `StatusEnum` above are objects, it is not possible to submit it via HTTP. +As described in the [documentation](https://symfony.com/doc/current/reference/forms/types/choice.html#choice-value) Symfony will use an incrementing integer as value. +Example, with `StatusEnum` above: +- `0` will be the value for `MemberStatus::NEW` +- `1` will be the value for `MemberStatus::VALIDATED` +- `2` will be the value for `MemberStatus::DISABLED` + +But, if you do not like this behavior, you can configure the form to use values instead: +```php +add('status', EnumType::class, [ + 'enum' => StatusEnum::class, + 'enum_choice_value' => true, + ]); + } +} +``` + +Now, still with `StatusEnum` above: +- `"new"` will be the value for `MemberStatus::NEW` +- `"validated"` will be the value for `MemberStatus::VALIDATED` +- `"disabled"` will be the value for `MemberStatus::DISABLED` diff --git a/docs/native-enum-integration.md b/docs/native-enum-integration.md new file mode 100644 index 0000000..3b92420 --- /dev/null +++ b/docs/native-enum-integration.md @@ -0,0 +1,107 @@ +# PHP native enum integration + +Let say that you already has such enum, from [PHP](https://www.php.net/manual/en/language.enumerations.php). + +```php + **Note** +> Here, we are using a `StringBackedEnum`, but it is not required. +> The bundle supports any form of `UnitEnum`, backed or not. +> https://www.php.net/manual/en/language.enumerations.backed.php + +## Standard enum + +If you want to integrate with the bundle, you just have to declare an enum for that class. + +```php +add('status', EnumType::class, [ + 'enum' => StatusEnum::class, + 'enum_choice_value' => true, + ]); + } +} +``` + +Now, still with `StatusEnum` above: +- `"new"` will be the value for `MemberStatus::NEW` +- `"validated"` will be the value for `MemberStatus::VALIDATED` +- `"disabled"` will be the value for `MemberStatus::DISABLED` diff --git a/src/Form/Type/EnumType.php b/src/Form/Type/EnumType.php index 2c7621e..6c8e800 100644 --- a/src/Form/Type/EnumType.php +++ b/src/Form/Type/EnumType.php @@ -4,6 +4,7 @@ namespace Yokai\EnumBundle\Form\Type; +use MyCLabs\Enum\Enum; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\OptionsResolver\Options; @@ -34,6 +35,7 @@ public function __construct(EnumRegistry $enumRegistry) public function configureOptions(OptionsResolver $resolver): void { $resolver + ->setDefined(['enum', 'enum_choice_value']) ->setRequired('enum') ->setAllowedValues( 'enum', @@ -44,7 +46,46 @@ function (string $name): bool { ->setDefault( 'choices', function (Options $options): array { - return $this->enumRegistry->get($options['enum'])->getChoices(); + $choices = $this->enumRegistry->get($options['enum'])->getChoices(); + + if ($options['enum_choice_value'] === null) { + foreach ($choices as $value) { + if (!\is_scalar($value)) { + @\trigger_error( + 'Not configuring the "enum_choice_value" option is deprecated.' . + ' It will default to "true" in 5.0.', + \E_USER_DEPRECATED + ); + break; + } + } + } + + return $choices; + } + ) + ->setAllowedTypes('enum_choice_value', ['bool', 'null']) + ->setDefault('enum_choice_value', null) + ->setDefault( + 'choice_value', + static function (Options $options) { + if ($options['enum_choice_value'] !== true) { + return null; + } + + return function ($value) { + if ($value instanceof \BackedEnum) { + return $value->value; + } + if ($value instanceof \UnitEnum) { + return $value->name; + } + if ($value instanceof Enum) { + return $value->getValue(); + } + + return $value; + }; } ) ; diff --git a/tests/Unit/Form/TestExtension.php b/tests/Unit/Form/TestExtension.php index 3343154..e58d67b 100644 --- a/tests/Unit/Form/TestExtension.php +++ b/tests/Unit/Form/TestExtension.php @@ -54,6 +54,6 @@ protected function loadTypeGuesser(): ?EnumTypeGuesser return null; } - return new EnumTypeGuesser($this->metadataFactory, $this->enumRegistry); + return new EnumTypeGuesser($this->metadataFactory); } } diff --git a/tests/Unit/Form/Type/EnumTypeTest.php b/tests/Unit/Form/Type/EnumTypeTest.php index f058aed..e4f89f8 100644 --- a/tests/Unit/Form/Type/EnumTypeTest.php +++ b/tests/Unit/Form/Type/EnumTypeTest.php @@ -10,8 +10,15 @@ use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; use Yokai\EnumBundle\EnumRegistry; use Yokai\EnumBundle\Form\Type\EnumType; +use Yokai\EnumBundle\NativeEnum; +use Yokai\EnumBundle\Tests\Unit\Fixtures\Action; +use Yokai\EnumBundle\Tests\Unit\Fixtures\ActionEnum; +use Yokai\EnumBundle\Tests\Unit\Fixtures\HTTPMethod; +use Yokai\EnumBundle\Tests\Unit\Fixtures\HTTPStatus; +use Yokai\EnumBundle\Tests\Unit\Fixtures\Picture; use Yokai\EnumBundle\Tests\Unit\Fixtures\StateEnum; use Yokai\EnumBundle\Tests\Unit\Form\TestExtension; +use Yokai\EnumBundle\Tests\Unit\Translator; /** * @author Yann Eugoné @@ -40,19 +47,109 @@ public function testEnumOptionValid(): void ); } + /** + * @dataProvider submit + */ + public function testSubmit($enum, $options, $data, $expected): void + { + $form = $this->createForm($enum, $options); + $form->submit($data); + self::assertEquals($expected, $form->getData()); + } + + public static function submit(): \Generator + { + yield [ + StateEnum::class, + [], + 'new', + 'new', + ]; + yield [ + StateEnum::class, + ['enum_choice_value' => true], + 'new', + 'new', + ]; + + yield [ + ActionEnum::class, + [], + 1, + Action::EDIT(), + ]; + yield [ + ActionEnum::class, + ['enum_choice_value' => true], + 'edit', + Action::EDIT(), + ]; + + if (\PHP_VERSION_ID < 80100) { + return; + } + + yield [ + Picture::class, + [], + 0, + Picture::Landscape, + ]; + yield [ + Picture::class, + ['enum_choice_value' => true], + 'Landscape', + Picture::Landscape, + ]; + + yield [ + HTTPMethod::class, + [], + 0, + HTTPMethod::GET, + ]; + yield [ + HTTPMethod::class, + ['enum_choice_value' => true], + 'get', + HTTPMethod::GET, + ]; + + yield [ + HTTPStatus::class, + ['enum_choice_value' => true], + 200, + HTTPStatus::OK, + ]; + yield [ + HTTPStatus::class, + [], + 0, + HTTPStatus::OK, + ]; + } + protected function getExtensions(): array { $enumRegistry = new EnumRegistry(); $enumRegistry->add(new StateEnum()); + $enumRegistry->add(new ActionEnum(new Translator([ + 'action.VIEW' => 'Voir', + 'action.EDIT' => 'Modifier', + ]))); + if (\PHP_VERSION_ID >= 80100) { + $enumRegistry->add(new NativeEnum(Picture::class)); + $enumRegistry->add(new NativeEnum(HTTPMethod::class)); + $enumRegistry->add(new NativeEnum(HTTPStatus::class)); + } return [ new TestExtension($enumRegistry) ]; } - private function createForm($enum = null): FormInterface + private function createForm($enum = null, $options = []): FormInterface { - $options = []; if ($enum) { $options['enum'] = $enum; }