Added a ConstraintViolationListNormalizer
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
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
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
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
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)
) {
// ...
}
}
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.
Great news !