diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index b36b179775671..d6eac5c79d609 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -73,6 +73,7 @@
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
+use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
@@ -1227,6 +1228,10 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
$container->removeDefinition('serializer.normalizer.dateinterval');
}
+ if (!class_exists(ConstraintViolationListNormalizer::class)) {
+ $container->removeDefinition('serializer.normalizer.constraint_violation_list');
+ }
+
if (!class_exists(ClassDiscriminatorFromClassMetadata::class)) {
$container->removeAlias('Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface');
$container->removeDefinition('serializer.mapping.class_discriminator_resolver');
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
index 90c4d1c5b5050..54b0c484d20bb 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
@@ -31,6 +31,11 @@
+
+
+
+
+
diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md
index d45e771e610b8..9c1dccb5d9fe9 100644
--- a/src/Symfony/Component/Serializer/CHANGELOG.md
+++ b/src/Symfony/Component/Serializer/CHANGELOG.md
@@ -13,6 +13,7 @@ CHANGELOG
maximum depth is reached
* added optional `int[] $ignoredNodeTypes` argument to `XmlEncoder::__construct`. XML decoding now
ignores comment node types by default.
+* added `ConstraintViolationListNormalizer`
4.0.0
-----
@@ -33,7 +34,7 @@ CHANGELOG
* added support for serializing `DateInterval` objects
* added getter for extra attributes in `ExtraAttributesException`
* improved `CsvEncoder` to handle variable nested structures
- * CSV headers can be passed to the `CsvEncoder` via the `csv_headers` serialization context variable
+ * CSV headers can be passed to the `CsvEncoder` via the `csv_headers` serialization context variable
* added `$context` when checking for encoding, decoding and normalizing in `Serializer`
3.3.0
diff --git a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php
new file mode 100644
index 0000000000000..68a4cb9213279
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
+/**
+ * A normalizer that normalizes a ConstraintViolationListInterface instance.
+ *
+ * This Normalizer implements RFC7807 {@link https://tools.ietf.org/html/rfc7807}.
+ *
+ *
+ * @author Grégoire Pineau
+ * @author Kévin Dunglas
+ */
+class ConstraintViolationListNormalizer implements NormalizerInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function normalize($object, $format = null, array $context = array())
+ {
+ $violations = array();
+ $messages = array();
+ foreach ($object as $violation) {
+ $violations[] = array(
+ 'propertyPath' => $violation->getPropertyPath(),
+ 'message' => $violation->getMessage(),
+ 'code' => $violation->getCode(),
+ );
+ $propertyPath = $violation->getPropertyPath();
+ $prefix = $propertyPath ? sprintf('%s: ', $propertyPath) : '';
+ $messages[] = $prefix.$violation->getMessage();
+ }
+
+ return array(
+ 'title' => isset($context['title']) ? $context['title'] : 'An error occurred',
+ 'detail' => $messages ? implode("\n", $messages) : '',
+ 'violations' => $violations,
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supportsNormalization($data, $format = null)
+ {
+ return $data instanceof ConstraintViolationListInterface;
+ }
+}
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php
new file mode 100644
index 0000000000000..5c9c55028ff2f
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Tests\Normalizer;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationList;
+
+/**
+ * @author Grégoire Pineau
+ * @author Kévin Dunglas
+ */
+class ConstraintViolationListNormalizerTest extends TestCase
+{
+ private $normalizer;
+
+ protected function setUp()
+ {
+ $this->normalizer = new ConstraintViolationListNormalizer();
+ }
+
+ public function testSupportsNormalization()
+ {
+ $this->assertTrue($this->normalizer->supportsNormalization(new ConstraintViolationList()));
+ $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
+ }
+
+ public function testNormalize()
+ {
+ $list = new ConstraintViolationList(array(
+ new ConstraintViolation('a', 'b', array(), 'c', 'd', 'e', null, 'f'),
+ new ConstraintViolation('1', '2', array(), '3', '4', '5', null, '6'),
+ ));
+
+ $expected = array(
+ 'title' => 'An error occurred',
+ 'detail' => 'd: a
+4: 1',
+ 'violations' => array(
+ array(
+ 'propertyPath' => 'd',
+ 'message' => 'a',
+ 'code' => 'f',
+ ),
+ array(
+ 'propertyPath' => '4',
+ 'message' => '1',
+ 'code' => '6',
+ ),
+ ),
+ );
+
+ $this->assertEquals($expected, $this->normalizer->normalize($list));
+ }
+}
diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json
index 4de54606d928d..c950a39c3cb28 100644
--- a/src/Symfony/Component/Serializer/composer.json
+++ b/src/Symfony/Component/Serializer/composer.json
@@ -25,6 +25,7 @@
"symfony/http-foundation": "~3.4|~4.0",
"symfony/cache": "~3.4|~4.0",
"symfony/property-info": "~3.4|~4.0",
+ "symfony/validator": "~3.4|~4.0",
"doctrine/annotations": "~1.0",
"symfony/dependency-injection": "~3.4|~4.0",
"doctrine/cache": "~1.0",