New in Symfony 4.1: Serializer improvements

Added a ConstraintViolationListNormalizer

Contributed by
Grégoire Pineau
in #22150.

When working on APIs with Symfony, it's common to use code like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * @Route("/blog/new", name="api_blog_new")
 * @Method("POST")
 * @Security("is_granted('ROLE_ADMIN')")
 */
public function new(Request $request, SerializerInterface $serializer, ValidatorInterface $validator)
{
    $data = $request->getContent();
    $post = $serializer->deserialize($data, Post::class, 'json', ['groups' => ['post_write']]);
    $post->setAuthor($this->getUser());

    $violations = $validator->validate($post);
    if (count($violations) > 0) {
        $repr = $serializer->serialize($violations, 'json');

        return JsonResponse::fromJsonString($repr, 400);
    }

    // ...
}

The $violations variable contains a ConstraintViolationList object and it's common to transform it into a list of errors and serialize the list to include it in a JSON response. That's why in Symfony 4.1 we've added a ConstraintViolationListNormalizer which does that for you automatically. The normalizer follows the RFC 7807 specification to generate the list of errors.

Getting the XML and CSV results as a collection

Contributed by
Hamza Amrouche
in #25218 and #25369.

The CsvEncoder and XmlEncoder now define a new config option called as_collection. If you pass that option as part of the context argument and set it to true, the results will be a collection.

Default constructor arguments for denormalization

Contributed by
Maxime Veber
in #25493.

If the constructor of a class defines arguments, as usually happens when using Value Objects, the serializer won't be able to create the object. In Symfony 4.1 we've introduced a new default_constructor_arguments context option to solve this problem.

In the following example, both foo and bar are required constructor arguments but only foo is provided. The value of bar is taken from the default_constructor_arguments option:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

class MyObj
{
    private $foo;
    private $bar;

    public function __construct($foo, $bar)
    {
        $this->foo = $foo;
        $this->bar = $bar;
    }
}

$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer(array($normalizer));

// this is equivalent to $data = new MyObj('Hello', '');
$data = $serializer->denormalize(['foo' => 'Hello'], 'MyObj', [
    'default_constructor_arguments' => [
        'MyObj' => ['foo' => '', 'bar' => ''],
    ]
]);

Added a MaxDepth handler

Contributed by
Kévin Dunglas
in #26108.

Sometimes, instead of just stopping the serialization process when the configured max depth is reached, it's better to let the developer handle this situation to return something (e.g. the identifier of the entity).

In Symfony 4.1 you can solve this problem defining a custom handler with the new setMaxDepthHandler() method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

class Foo
{
    public $id;

    /** @MaxDepth(1) */
    public $child;
}

$level1 = new Foo();
$level1->id = 1;

$level2 = new Foo();
$level2->id = 2;
$level1->child = $level2;

$level3 = new Foo();
$level3->id = 3;
$level2->child = $level3;

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer($classMetadataFactory);
$normalizer->setMaxDepthHandler(function ($foo) {
    return '/foos/'.$foo->id;
});

$serializer = new Serializer(array($normalizer));
$result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true));
/*
$result = array[
    'id' => 1,
    'child' => [
        'id' => 2,
        'child' => '/foos/3',
    ]
];
*/

Ignore comments when decoding XML

Contributed by
James Sansbury
in #26445.

In previous Symfony versions, XML comments were processed when decoding contents. Also, if the first line of the XML content was a comment, it was used as the root node of the decoded XML.

In Symfony 4.1, XML comments are removed by default but you can control this behavior with the new optional third constructor argument:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class XmlEncoder
{
    public function __construct(
        string $rootNodeName = 'response',
        int $loadOptions = null,
        array $ignoredNodeTypes = array(XML_PI_NODE, XML_COMMENT_NODE)
    ) {
        // ...
    }
}

Comments

These are some really handy changes. Thanks!
Great work!🤘
Nice work! Waiting for these a long time, and now waiting more sf 4.1
That is a great Idea.
Login with SensioLabsConnect to post a comment