From dcb8d8b05dc1c95e9c7434e933032f7e8e78ef66 Mon Sep 17 00:00:00 2001 From: YaFou <33806646+YaFou@users.noreply.github.com> Date: Mon, 22 Jun 2020 18:01:27 +0200 Subject: [PATCH] [Serializer] Adds FormErrorNormalizer --- .../FrameworkBundle/Resources/config/form.php | 4 + .../FrameworkExtensionTest.php | 12 ++ src/Symfony/Component/Form/CHANGELOG.md | 5 + .../Form/Serializer/FormErrorNormalizer.php | 94 ++++++++++ .../Serializer/FormErrorNormalizerTest.php | 163 ++++++++++++++++++ src/Symfony/Component/Form/composer.json | 1 + 6 files changed, 279 insertions(+) create mode 100644 src/Symfony/Component/Form/Serializer/FormErrorNormalizer.php create mode 100644 src/Symfony/Component/Form/Tests/Serializer/FormErrorNormalizerTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.php index 50bd1e3067..94845bd4ab 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.php @@ -34,6 +34,7 @@ use Symfony\Component\Form\FormRegistry; use Symfony\Component\Form\FormRegistryInterface; use Symfony\Component\Form\ResolvedFormTypeFactory; use Symfony\Component\Form\ResolvedFormTypeFactoryInterface; +use Symfony\Component\Form\Serializer\FormErrorNormalizer; use Symfony\Component\Form\Util\ServerParams; return static function (ContainerConfigurator $container) { @@ -140,5 +141,8 @@ return static function (ContainerConfigurator $container) { param('validator.translation_domain'), ]) ->tag('form.type_extension') + + ->set('form.serializer.normalizer.form_error', FormErrorNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 26dab6327b..2c14a2ad0b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -40,6 +40,7 @@ use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\Serializer\FormErrorNormalizer; use Symfony\Component\HttpClient\ScopingHttpClient; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use Symfony\Component\Messenger\Transport\TransportFactory; @@ -1150,6 +1151,17 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertEquals(-910, $tag[0]['priority']); } + public function testFormErrorNormalizerRegistred() + { + $container = $this->createContainerFromFile('full'); + + $definition = $container->getDefinition('form.serializer.normalizer.form_error'); + $tag = $definition->getTag('serializer.normalizer'); + + $this->assertEquals(FormErrorNormalizer::class, $definition->getClass()); + $this->assertEquals(-915, $tag[0]['priority']); + } + public function testJsonSerializableNormalizerRegistered() { $container = $this->createContainerFromFile('full'); diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index d242d3dbee..439a5c9007 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.2.0 +----- + +* added `FormErrorNormalizer` + 5.1.0 ----- diff --git a/src/Symfony/Component/Form/Serializer/FormErrorNormalizer.php b/src/Symfony/Component/Form/Serializer/FormErrorNormalizer.php new file mode 100644 index 0000000000..3f57dd6779 --- /dev/null +++ b/src/Symfony/Component/Form/Serializer/FormErrorNormalizer.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Serializer; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +/** + * Normalizes invalid Form instances. + */ +final class FormErrorNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface +{ + const TITLE = 'title'; + const TYPE = 'type'; + const CODE = 'status_code'; + + /** + * {@inheritdoc} + */ + public function normalize($object, $format = null, array $context = []): array + { + $data = [ + 'title' => $context[self::TITLE] ?? 'Validation Failed', + 'type' => $context[self::TYPE] ?? 'https://symfony.com/errors/form', + 'code' => $context[self::CODE] ?? null, + 'errors' => $this->convertFormErrorsToArray($object), + ]; + + if (0 !== \count($object->all())) { + $data['children'] = $this->convertFormChildrenToArray($object); + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid(); + } + + private function convertFormErrorsToArray(FormInterface $data): array + { + $errors = []; + + foreach ($data->getErrors() as $error) { + $errors[] = [ + 'message' => $error->getMessage(), + 'cause' => $error->getCause(), + ]; + } + + return $errors; + } + + private function convertFormChildrenToArray(FormInterface $data): array + { + $children = []; + + foreach ($data->all() as $child) { + $childData = [ + 'errors' => $this->convertFormErrorsToArray($child), + ]; + + if (!empty($child->all())) { + $childData['children'] = $this->convertFormChildrenToArray($child); + } + + $children[$child->getName()] = $childData; + } + + return $children; + } + + /** + * {@inheritdoc} + */ + public function hasCacheableSupportsMethod(): bool + { + return __CLASS__ === static::class; + } +} diff --git a/src/Symfony/Component/Form/Tests/Serializer/FormErrorNormalizerTest.php b/src/Symfony/Component/Form/Tests/Serializer/FormErrorNormalizerTest.php new file mode 100644 index 0000000000..45886d82c1 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Serializer/FormErrorNormalizerTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Serializer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormErrorIterator; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Serializer\FormErrorNormalizer; + +class FormErrorNormalizerTest extends TestCase +{ + /** + * @var FormErrorNormalizer + */ + private $normalizer; + + /** + * @var FormInterface + */ + private $form; + + protected function setUp(): void + { + $this->normalizer = new FormErrorNormalizer(); + + $this->form = $this->createMock(FormInterface::class); + $this->form->method('isSubmitted')->willReturn(true); + $this->form->method('all')->willReturn([]); + + $this->form->method('getErrors') + ->willReturn(new FormErrorIterator($this->form, [ + new FormError('a', 'b', ['c', 'd'], 5, 'f'), + new FormError(1, 2, [3, 4], 5, 6), + ]) + ); + } + + public function testSupportsNormalizationWithWrongClass() + { + $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); + } + + public function testSupportsNormalizationWithNotSubmittedForm() + { + $form = $this->createMock(FormInterface::class); + $this->assertFalse($this->normalizer->supportsNormalization($form)); + } + + public function testSupportsNormalizationWithValidForm() + { + $this->assertTrue($this->normalizer->supportsNormalization($this->form)); + } + + public function testNormalize() + { + $expected = [ + 'code' => null, + 'title' => 'Validation Failed', + 'type' => 'https://symfony.com/errors/form', + 'errors' => [ + [ + 'message' => 'a', + 'cause' => 'f', + ], + [ + 'message' => '1', + 'cause' => 6, + ], + ], + ]; + + $this->assertEquals($expected, $this->normalizer->normalize($this->form)); + } + + public function testNormalizeWithChildren() + { + $exptected = [ + 'code' => null, + 'title' => 'Validation Failed', + 'type' => 'https://symfony.com/errors/form', + 'errors' => [ + [ + 'message' => 'a', + 'cause' => null, + ], + ], + 'children' => [ + 'form1' => [ + 'errors' => [ + [ + 'message' => 'b', + 'cause' => null, + ], + ], + ], + 'form2' => [ + 'errors' => [ + [ + 'message' => 'c', + 'cause' => null, + ], + ], + 'children' => [ + 'form3' => [ + 'errors' => [ + [ + 'message' => 'd', + 'cause' => null, + ], + ], + ], + ], + ], + ], + ]; + + $form = clone $form1 = clone $form2 = clone $form3 = $this->createMock(FormInterface::class); + + $form1->method('getErrors') + ->willReturn(new FormErrorIterator($form1, [ + new FormError('b'), + ]) + ); + $form1->method('getName')->willReturn('form1'); + + $form2->method('getErrors') + ->willReturn(new FormErrorIterator($form1, [ + new FormError('c'), + ]) + ); + $form2->method('getName')->willReturn('form2'); + + $form3->method('getErrors') + ->willReturn(new FormErrorIterator($form1, [ + new FormError('d'), + ]) + ); + $form3->method('getName')->willReturn('form3'); + + $form2->method('all')->willReturn([$form3]); + + $form = $this->createMock(FormInterface::class); + $form->method('isSubmitted')->willReturn(true); + $form->method('all')->willReturn([$form1, $form2]); + $form->method('getErrors') + ->willReturn(new FormErrorIterator($form, [ + new FormError('a'), + ]) + ); + + $this->assertEquals($exptected, $this->normalizer->normalize($form)); + } +} diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 1f37f7b7ca..4cc57f83e4 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -37,6 +37,7 @@ "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", "symfony/security-csrf": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0", "symfony/translation": "^4.4|^5.0", "symfony/var-dumper": "^4.4|^5.0" },