From 8a1497b1f27242aa08b702f8f65150ec25f75190 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 21 Jan 2023 16:42:58 +0100 Subject: [PATCH] Combine component and framework docs for Serializer --- _build/redirection_map | 4 +- .../serializer/serializer_workflow.svg | 0 .../serializer/serializer_workflow.dia | Bin components/property_access.rst | 2 + components/property_info.rst | 6 +- components/serializer.rst | 1948 -------------- reference/attributes.rst | 10 +- reference/configuration/framework.rst | 9 +- reference/twig_reference.rst | 2 + serializer.rst | 2262 ++++++++++++++--- serializer/custom_context_builders.rst | 8 +- serializer/custom_encoders.rst | 61 - serializer/custom_name_converter.rst | 105 + serializer/custom_normalizer.rst | 68 +- serializer/encoders.rst | 371 +++ 15 files changed, 2465 insertions(+), 2391 deletions(-) rename _images/{components => }/serializer/serializer_workflow.svg (100%) rename _images/sources/{components => }/serializer/serializer_workflow.dia (100%) delete mode 100644 components/serializer.rst delete mode 100644 serializer/custom_encoders.rst create mode 100644 serializer/custom_name_converter.rst create mode 100644 serializer/encoders.rst diff --git a/_build/redirection_map b/_build/redirection_map index 3ad55f95c73..4fb14724d26 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -525,7 +525,7 @@ /testing/functional_tests_assertions /testing#testing-application-assertions /components https://symfony.com/components /components/index https://symfony.com/components -/serializer/normalizers /components/serializer#normalizers +/serializer/normalizers /serializer#serializer-built-in-normalizers /logging/monolog_regex_based_excludes /logging/monolog_exclude_http_codes /security/named_encoders /security/named_hashers /components/inflector /components/string#inflector @@ -566,3 +566,5 @@ /messenger/dispatch_after_current_bus /messenger#messenger-transactional-messages /messenger/multiple_buses /messenger#messenger-multiple-buses /frontend/encore/server-data /frontend/server-data +/components/serializer /serializer +/serializer/custom_encoder /serializer/encoders#serializer-custom-encoder diff --git a/_images/components/serializer/serializer_workflow.svg b/_images/serializer/serializer_workflow.svg similarity index 100% rename from _images/components/serializer/serializer_workflow.svg rename to _images/serializer/serializer_workflow.svg diff --git a/_images/sources/components/serializer/serializer_workflow.dia b/_images/sources/serializer/serializer_workflow.dia similarity index 100% rename from _images/sources/components/serializer/serializer_workflow.dia rename to _images/sources/serializer/serializer_workflow.dia diff --git a/components/property_access.rst b/components/property_access.rst index ba487135d94..717012d6710 100644 --- a/components/property_access.rst +++ b/components/property_access.rst @@ -26,6 +26,8 @@ default configuration:: $propertyAccessor = PropertyAccess::createPropertyAccessor(); +.. _property-access-reading-arrays: + Reading from Arrays ------------------- diff --git a/components/property_info.rst b/components/property_info.rst index e9f5853cb51..6d57c1bb274 100644 --- a/components/property_info.rst +++ b/components/property_info.rst @@ -458,9 +458,9 @@ SerializerExtractor This extractor depends on the `symfony/serializer`_ library. -Using :ref:`groups metadata ` -from the :doc:`Serializer component `, -the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor` +Using :ref:`groups metadata ` from the +:doc:`Serializer component `, the +:class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor` provides list information. This extractor is *not* registered automatically with the ``property_info`` service in the Symfony Framework:: diff --git a/components/serializer.rst b/components/serializer.rst deleted file mode 100644 index c10e4c7e45f..00000000000 --- a/components/serializer.rst +++ /dev/null @@ -1,1948 +0,0 @@ -The Serializer Component -======================== - - The Serializer component is meant to be used to turn objects into a - specific format (XML, JSON, YAML, ...) and the other way around. - -In order to do so, the Serializer component follows the following schema. - -.. raw:: html - - - -When (de)serializing objects, the Serializer uses an array as the intermediary -between objects and serialized contents. Encoders will only deal with -turning specific **formats** into **arrays** and vice versa. The same way, -normalizers will deal with turning specific **objects** into **arrays** and -vice versa. The Serializer deals with calling the normalizers and encoders -when serializing objects or deserializing formats. - -Serialization is a complex topic. This component may not cover all your use -cases out of the box, but it can be useful for developing tools to -serialize and deserialize your objects. - -Installation ------------- - -.. code-block:: terminal - - $ composer require symfony/serializer - -.. include:: /components/require_autoload.rst.inc - -To use the ``ObjectNormalizer``, the :doc:`PropertyAccess component ` -must also be installed. - -Usage ------ - -.. seealso:: - - This article explains the philosophy of the Serializer and gets you familiar - with the concepts of normalizers and encoders. The code examples assume - that you use the Serializer as an independent component. If you are using - the Serializer in a Symfony application, read :doc:`/serializer` after you - finish this article. - -To use the Serializer component, set up the -:class:`Symfony\\Component\\Serializer\\Serializer` specifying which encoders -and normalizer are going to be available:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Encoder\XmlEncoder; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $encoders = [new XmlEncoder(), new JsonEncoder()]; - $normalizers = [new ObjectNormalizer()]; - - $serializer = new Serializer($normalizers, $encoders); - -The preferred normalizer is the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`, -but other normalizers are available. All the examples shown below use -the ``ObjectNormalizer``. - -Serializing an Object ---------------------- - -For the sake of this example, assume the following class already -exists in your project:: - - namespace App\Model; - - class Person - { - private int $age; - private string $name; - private bool $sportsperson; - private ?\DateTimeInterface $createdAt; - - // Getters - public function getAge(): int - { - return $this->age; - } - - public function getName(): string - { - return $this->name; - } - - public function getCreatedAt(): ?\DateTimeInterface - { - return $this->createdAt; - } - - // Issers - public function isSportsperson(): bool - { - return $this->sportsperson; - } - - // Setters - public function setAge(int $age): void - { - $this->age = $age; - } - - public function setName(string $name): void - { - $this->name = $name; - } - - public function setSportsperson(bool $sportsperson): void - { - $this->sportsperson = $sportsperson; - } - - public function setCreatedAt(?\DateTimeInterface $createdAt = null): void - { - $this->createdAt = $createdAt; - } - } - -Now, if you want to serialize this object into JSON, you only need to -use the Serializer service created before:: - - use App\Model\Person; - - $person = new Person(); - $person->setName('foo'); - $person->setAge(99); - $person->setSportsperson(false); - - $jsonContent = $serializer->serialize($person, 'json'); - - // $jsonContent contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null} - - echo $jsonContent; // or return it in a Response - -The first parameter of the :method:`Symfony\\Component\\Serializer\\Serializer::serialize` -is the object to be serialized and the second is used to choose the proper encoder, -in this case :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`. - -Deserializing an Object ------------------------ - -You'll now learn how to do the exact opposite. This time, the information -of the ``Person`` class would be encoded in XML format:: - - use App\Model\Person; - - $data = << - foo - 99 - false - - EOF; - - $person = $serializer->deserialize($data, Person::class, 'xml'); - -In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize` -needs three parameters: - -#. The information to be decoded -#. The name of the class this information will be decoded to -#. The encoder used to convert that information into an array - -By default, additional attributes that are not mapped to the denormalized object -will be ignored by the Serializer component. If you prefer to throw an exception -when this happens, set the ``AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES`` context option to -``false`` and provide an object that implements ``ClassMetadataFactoryInterface`` -when constructing the normalizer:: - - use App\Model\Person; - - $data = << - foo - 99 - Paris - - EOF; - - // $loader is any of the valid loaders explained later in this article - $classMetadataFactory = new ClassMetadataFactory($loader); - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - // this will throw a Symfony\Component\Serializer\Exception\ExtraAttributesException - // because "city" is not an attribute of the Person class - $person = $serializer->deserialize($data, Person::class, 'xml', [ - AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, - ]); - -Deserializing in an Existing Object -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The serializer can also be used to update an existing object:: - - // ... - $person = new Person(); - $person->setName('bar'); - $person->setAge(99); - $person->setSportsperson(true); - - $data = << - foo - 69 - - EOF; - - $serializer->deserialize($data, Person::class, 'xml', [AbstractNormalizer::OBJECT_TO_POPULATE => $person]); - // $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true) - -This is a common need when working with an ORM. - -The ``AbstractNormalizer::OBJECT_TO_POPULATE`` is only used for the top level object. If that object -is the root of a tree structure, all child elements that exist in the -normalized data will be re-created with new instances. - -When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option is set to -true, existing children of the root ``OBJECT_TO_POPULATE`` are updated from the -normalized data, instead of the denormalizer re-creating them. Note that -``DEEP_OBJECT_TO_POPULATE`` only works for single child objects, but not for -arrays of objects. Those will still be replaced when present in the normalized -data. - -Context -------- - -Many Serializer features can be configured :ref:`using a context `. - -.. _component-serializer-attributes-groups: - -Attributes Groups ------------------ - -Sometimes, you want to serialize different sets of attributes from your -entities. Groups are a handy way to achieve this need. - -Assume you have the following plain-old-PHP object:: - - namespace Acme; - - class MyObj - { - public string $foo; - - private string $bar; - - public function getBar(): string - { - return $this->bar; - } - - public function setBar($bar): string - { - return $this->bar = $bar; - } - } - -The definition of serialization can be specified using annotations, attributes, XML -or YAML. The :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory` -that will be used by the normalizer must be aware of the format to use. - -The following code shows how to initialize the :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory` -for each format: - -* Annotations in PHP files:: - - use Doctrine\Common\Annotations\AnnotationReader; - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; - - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - -* Attributes in PHP files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - -* YAML files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; - - $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml')); - -* XML files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; - - $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml')); - -.. versionadded:: 6.4 - - The - :class:`Symfony\\Component\\Serializer\\Mapping\\Loader\\AttributeLoader` - was introduced in Symfony 6.4. Prior to this, the - :class:`Symfony\\Component\\Serializer\\Mapping\\Loader\\AnnotationLoader` - must be used. - -.. deprecated:: 6.4 - - Reading annotations in PHP files is deprecated since Symfony 6.4. - Also, the - :class:`Symfony\\Component\\Serializer\\Mapping\\Loader\\AnnotationLoader` - was deprecated in Symfony 6.4. - -.. _component-serializer-attributes-groups-annotations: -.. _component-serializer-attributes-groups-attributes: - -Then, create your groups definition: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace Acme; - - use Symfony\Component\Serializer\Annotation\Groups; - - class MyObj - { - #[Groups(['group1', 'group2'])] - public string $foo; - - #[Groups(['group4'])] - public string $anotherProperty; - - #[Groups(['group3'])] - public function getBar() // is* methods are also supported - { - return $this->bar; - } - - // ... - } - - .. code-block:: yaml - - Acme\MyObj: - attributes: - foo: - groups: ['group1', 'group2'] - anotherProperty: - groups: ['group4'] - bar: - groups: ['group3'] - - .. code-block:: xml - - - - - - group1 - group2 - - - - group4 - - - - group3 - - - - -You are now able to serialize only attributes in the groups you want:: - - use Acme\MyObj; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $obj = new MyObj(); - $obj->foo = 'foo'; - $obj->anotherProperty = 'anotherProperty'; - $obj->setBar('bar'); - - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->normalize($obj, null, ['groups' => 'group1']); - // $data = ['foo' => 'foo']; - - $obj2 = $serializer->denormalize( - ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'], - MyObj::class, - null, - ['groups' => ['group1', 'group3']] - ); - // $obj2 = MyObj(foo: 'foo', bar: 'bar') - - // To get all groups, use the special value `*` in `groups` - $obj3 = $serializer->denormalize( - ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'], - MyObj::class, - null, - ['groups' => ['*']] - ); - // $obj2 = MyObj(foo: 'foo', anotherProperty: 'anotherProperty', bar: 'bar') - -.. _ignoring-attributes-when-serializing: - -Selecting Specific Attributes ------------------------------ - -It is also possible to serialize only a set of specific attributes:: - - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class User - { - public string $familyName; - public string $givenName; - public Company $company; - } - - class Company - { - public string $name; - public string $address; - } - - $company = new Company(); - $company->name = 'Les-Tilleuls.coop'; - $company->address = 'Lille, France'; - - $user = new User(); - $user->familyName = 'Dunglas'; - $user->givenName = 'Kévin'; - $user->company = $company; - - $serializer = new Serializer([new ObjectNormalizer()]); - - $data = $serializer->normalize($user, null, [AbstractNormalizer::ATTRIBUTES => ['familyName', 'company' => ['name']]]); - // $data = ['familyName' => 'Dunglas', 'company' => ['name' => 'Les-Tilleuls.coop']]; - -Only attributes that are not ignored (see below) are available. -If some serialization groups are set, only attributes allowed by those groups can be used. - -As for groups, attributes can be selected during both the serialization and deserialization processes. - -.. _serializer_ignoring-attributes: - -Ignoring Attributes -------------------- - -All accessible attributes are included by default when serializing objects. -There are two options to ignore some of those attributes. - -Option 1: Using ``#[Ignore]`` Attribute -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App\Model; - - use Symfony\Component\Serializer\Annotation\Ignore; - - class MyClass - { - public string $foo; - - #[Ignore] - public string $bar; - } - - .. code-block:: yaml - - App\Model\MyClass: - attributes: - bar: - ignore: true - - .. code-block:: xml - - - - - - - - -You can now ignore specific attributes during serialization:: - - use App\Model\MyClass; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $obj = new MyClass(); - $obj->foo = 'foo'; - $obj->bar = 'bar'; - - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->normalize($obj); - // $data = ['foo' => 'foo']; - -Option 2: Using the Context -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pass an array with the names of the attributes to ignore using the -``AbstractNormalizer::IGNORED_ATTRIBUTES`` key in the ``context`` of the -serializer method:: - - use Acme\Person; - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $person = new Person(); - $person->setName('foo'); - $person->setAge(99); - - $normalizer = new ObjectNormalizer(); - $encoder = new JsonEncoder(); - - $serializer = new Serializer([$normalizer], [$encoder]); - $serializer->serialize($person, 'json', [AbstractNormalizer::IGNORED_ATTRIBUTES => ['age']]); // Output: {"name":"foo"} - -.. _component-serializer-converting-property-names-when-serializing-and-deserializing: - -Converting Property Names when Serializing and Deserializing ------------------------------------------------------------- - -Sometimes serialized attributes must be named differently than properties -or getter/setter methods of PHP classes. - -The Serializer component provides a handy way to translate or map PHP field -names to serialized names: The Name Converter System. - -Given you have the following object:: - - class Company - { - public string $name; - public string $address; - } - -And in the serialized form, all attributes must be prefixed by ``org_`` like -the following:: - - {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} - -A custom name converter can handle such cases:: - - use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - - class OrgPrefixNameConverter implements NameConverterInterface - { - public function normalize(string $propertyName): string - { - return 'org_'.$propertyName; - } - - public function denormalize(string $propertyName): string - { - // removes 'org_' prefix - return str_starts_with($propertyName, 'org_') ? substr($propertyName, 4) : $propertyName; - } - } - -The custom name converter can be used by passing it as second parameter of any -class extending :class:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer`, -including :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` -and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $nameConverter = new OrgPrefixNameConverter(); - $normalizer = new ObjectNormalizer(null, $nameConverter); - - $serializer = new Serializer([$normalizer], [new JsonEncoder()]); - - $company = new Company(); - $company->name = 'Acme Inc.'; - $company->address = '123 Main Street, Big City'; - - $json = $serializer->serialize($company, 'json'); - // {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} - $companyCopy = $serializer->deserialize($json, Company::class, 'json'); - // Same data as $company - -.. note:: - - You can also implement - :class:`Symfony\\Component\\Serializer\\NameConverter\\AdvancedNameConverterInterface` - to access the current class name, format and context. - -.. _using-camelized-method-names-for-underscored-attributes: - -CamelCase to snake_case -~~~~~~~~~~~~~~~~~~~~~~~ - -In many formats, it's common to use underscores to separate words (also known -as snake_case). However, in Symfony applications is common to use CamelCase to -name properties (even though the `PSR-1 standard`_ doesn't recommend any -specific case for property names). - -Symfony provides a built-in name converter designed to transform between -snake_case and CamelCased styles during serialization and deserialization -processes:: - - use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - - $normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter()); - - class Person - { - public function __construct( - private string $firstName, - ) { - } - - public function getFirstName(): string - { - return $this->firstName; - } - } - - $kevin = new Person('Kévin'); - $normalizer->normalize($kevin); - // ['first_name' => 'Kévin']; - - $anne = $normalizer->denormalize(['first_name' => 'Anne'], 'Person'); - // Person object with firstName: 'Anne' - -.. _serializer_name-conversion: - -Configure name conversion using metadata -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using this component inside a Symfony application and the class metadata -factory is enabled as explained in the :ref:`Attributes Groups section `, -this is already set up and you only need to provide the configuration. Otherwise:: - - // ... - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); - - $serializer = new Serializer( - [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)], - ['json' => new JsonEncoder()] - ); - -Now configure your name conversion mapping. Consider an application that -defines a ``Person`` entity with a ``firstName`` property: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App\Entity; - - use Symfony\Component\Serializer\Annotation\SerializedName; - - class Person - { - public function __construct( - #[SerializedName('customer_name')] - private string $firstName, - ) { - } - - // ... - } - - .. code-block:: yaml - - App\Entity\Person: - attributes: - firstName: - serialized_name: customer_name - - .. code-block:: xml - - - - - - - - -This custom mapping is used to convert property names when serializing and -deserializing objects:: - - $serialized = $serializer->serialize(new Person('Kévin'), 'json'); - // {"customer_name": "Kévin"} - -Serializing Boolean Attributes ------------------------------- - -If you are using isser methods (methods prefixed by ``is``, like -``App\Model\Person::isSportsperson()``), the Serializer component will -automatically detect and use it to serialize related attributes. - -The ``ObjectNormalizer`` also takes care of methods starting with ``has``, ``get``, -and ``can``. - -.. versionadded:: 6.1 - - The support of canners (methods prefixed by ``can``) was introduced in Symfony 6.1. - -Using Callbacks to Serialize Properties with Object Instances -------------------------------------------------------------- - -When serializing, you can set a callback to format a specific object property:: - - use App\Model\Person; - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - use Symfony\Component\Serializer\Serializer; - - $encoder = new JsonEncoder(); - - // all callback parameters are optional (you can omit the ones you don't use) - $dateCallback = function (object $attributeValue, object $object, string $attributeName, ?string $format = null, array $context = []): string { - return $attributeValue instanceof \DateTime ? $attributeValue->format(\DateTime::ATOM) : ''; - }; - - $defaultContext = [ - AbstractNormalizer::CALLBACKS => [ - 'createdAt' => $dateCallback, - ], - ]; - - $normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer], [$encoder]); - - $person = new Person(); - $person->setName('cordoval'); - $person->setAge(34); - $person->setCreatedAt(new \DateTime('now')); - - $serializer->serialize($person, 'json'); - // Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"} - -.. _component-serializer-normalizers: - -Normalizers ------------ - -Normalizers turn **objects** into **arrays** and vice versa. They implement -:class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface` for -normalizing (object to array) and -:class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface` for -denormalizing (array to object). - -Normalizers are enabled in the serializer passing them as its first argument:: - - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $normalizers = [new ObjectNormalizer()]; - $serializer = new Serializer($normalizers, []); - -Built-in Normalizers -~~~~~~~~~~~~~~~~~~~~ - -The Serializer component provides several built-in normalizers: - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` - This normalizer leverages the :doc:`PropertyAccess Component ` - to read and write in the object. It means that it can access to properties - directly and through getters, setters, hassers, issers, canners, adders and removers. - It supports calling the constructor during the denormalization process. - - Objects are normalized to a map of property names and values (names are - generated by removing the ``get``, ``set``, ``has``, ``is``, ``can``, ``add`` or ``remove`` - prefix from the method name and transforming the first letter to lowercase; e.g. - ``getFirstName()`` -> ``firstName``). - - The ``ObjectNormalizer`` is the most powerful normalizer. It is configured by - default in Symfony applications with the Serializer component enabled. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` - This normalizer reads the content of the class by calling the "getters" - (public methods starting with "get"). It will denormalize data by calling - the constructor and the "setters" (public methods starting with "set"). - - Objects are normalized to a map of property names and values (names are - generated by removing the ``get`` prefix from the method name and transforming - the first letter to lowercase; e.g. ``getFirstName()`` -> ``firstName``). - -:class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer` - This normalizer directly reads and writes public properties as well as - **private and protected** properties (from both the class and all of its - parent classes) by using `PHP reflection`_. It supports calling the constructor - during the denormalization process. - - Objects are normalized to a map of property names to property values. - - If you prefer to only normalize certain properties (e.g. only public properties) - set the ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option and - combine the following values: ``PropertyNormalizer::NORMALIZE_PUBLIC``, - ``PropertyNormalizer::NORMALIZE_PROTECTED`` or ``PropertyNormalizer::NORMALIZE_PRIVATE``. - - .. versionadded:: 6.2 - - The ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option and its - values were introduced in Symfony 6.2. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer` - This normalizer works with classes that implement :phpclass:`JsonSerializable`. - - It will call the :phpmethod:`JsonSerializable::jsonSerialize` method and - then further normalize the result. This means that nested - :phpclass:`JsonSerializable` classes will also be normalized. - - This normalizer is particularly helpful when you want to gradually migrate - from an existing codebase using simple :phpfunction:`json_encode` to the Symfony - Serializer by allowing you to mix which normalizers are used for which classes. - - Unlike with :phpfunction:`json_encode` circular references can be handled. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` - This normalizer converts :phpclass:`DateTimeInterface` objects (e.g. - :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings. - By default, it uses the `RFC3339`_ format. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer` - This normalizer converts :phpclass:`DateTimeZone` objects into strings that - represent the name of the timezone according to the `list of PHP timezones`_. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` - This normalizer converts :phpclass:`SplFileInfo` objects into a `data URI`_ - string (``data:...``) such that files can be embedded into serialized data. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer` - This normalizer converts :phpclass:`DateInterval` objects into strings. - By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer` - This normalizer converts a \BackedEnum objects into strings or integers. - - By default, an exception is thrown when data is not a valid backed enumeration. If you - want ``null`` instead, you can set the ``BackedEnumNormalizer::ALLOW_INVALID_VALUES`` option. - - .. versionadded:: 6.3 - - The ``BackedEnumNormalizer::ALLOW_INVALID_VALUES`` context option was introduced in Symfony 6.3. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` - This normalizer works with classes that implement - :class:`Symfony\\Component\\Form\\FormInterface`. - - It will get errors from the form and normalize them into a normalized array. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer` - This normalizer converts objects that implement - :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface` - into a list of errors according to the `RFC 7807`_ standard. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` - Normalizes errors according to the API Problem spec `RFC 7807`_. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer` - Normalizes a PHP object using an object that implements :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface`. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\UidNormalizer` - This normalizer converts objects that extend - :class:`Symfony\\Component\\Uid\\AbstractUid` into strings. - The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Uuid` - is the `RFC 4122`_ format (example: ``d9e7a184-5d5b-11ea-a62a-3499710062d0``). - The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Ulid` - is the Base 32 format (example: ``01E439TP9XJZ9RPFH3T1PYBCR8``). - You can change the string format by setting the serializer context option - ``UidNormalizer::NORMALIZATION_FORMAT_KEY`` to ``UidNormalizer::NORMALIZATION_FORMAT_BASE_58``, - ``UidNormalizer::NORMALIZATION_FORMAT_BASE_32`` or ``UidNormalizer::NORMALIZATION_FORMAT_RFC_4122``. - - Also it can denormalize ``uuid`` or ``ulid`` strings to :class:`Symfony\\Component\\Uid\\Uuid` - or :class:`Symfony\\Component\\Uid\\Ulid`. The format does not matter. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer` - This normalizer converts objects that implement - :class:`Symfony\\Contracts\\Translation\\TranslatableInterface` into - translated strings, using the - :method:`Symfony\\Contracts\\Translation\\TranslatableInterface::trans` - method. You can define the locale to use to translate the object by - setting the ``TranslatableNormalizer::NORMALIZATION_LOCALE_KEY`` serializer - context option. - - .. versionadded:: 6.4 - - The :class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer` - was introduced in Symfony 6.4. - -.. note:: - - You can also create your own Normalizer to use another structure. Read more at - :doc:`/serializer/custom_normalizer`. - -Certain normalizers are enabled by default when using the Serializer component -in a Symfony application, additional ones can be enabled by tagging them with -:ref:`serializer.normalizer `. - -Here is an example of how to enable the built-in -:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`, a -faster alternative to the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - services: - # ... - - get_set_method_normalizer: - class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer - tags: [serializer.normalizer] - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // config/services.php - namespace Symfony\Component\DependencyInjection\Loader\Configurator; - - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - - return static function (ContainerConfigurator $container): void { - $container->services() - // ... - ->set('get_set_method_normalizer', GetSetMethodNormalizer::class) - ->tag('serializer.normalizer') - ; - }; - -.. _component-serializer-encoders: - -Encoders --------- - -Encoders turn **arrays** into **formats** and vice versa. They implement -:class:`Symfony\\Component\\Serializer\\Encoder\\EncoderInterface` -for encoding (array to format) and -:class:`Symfony\\Component\\Serializer\\Encoder\\DecoderInterface` for decoding -(format to array). - -You can add new encoders to a Serializer instance by using its second constructor argument:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Encoder\XmlEncoder; - use Symfony\Component\Serializer\Serializer; - - $encoders = [new XmlEncoder(), new JsonEncoder()]; - $serializer = new Serializer([], $encoders); - -Built-in Encoders -~~~~~~~~~~~~~~~~~ - -The Serializer component provides several built-in encoders: - -:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder` - This class encodes and decodes data in `JSON`_. - -:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder` - This class encodes and decodes data in `XML`_. - -:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder` - This encoder encodes and decodes data in `YAML`_. This encoder requires the - :doc:`Yaml Component `. - -:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder` - This encoder encodes and decodes data in `CSV`_. - -.. note:: - - You can also create your own Encoder to use another structure. Read more at - :doc:`/serializer/custom_encoders`. - -All these encoders are enabled by default when using the Serializer component -in a Symfony application. - -The ``JsonEncoder`` -~~~~~~~~~~~~~~~~~~~ - -The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP -:phpfunction:`json_encode` and :phpfunction:`json_decode` functions. It can be -useful to modify how these functions operate in certain instances by providing -options such as ``JSON_PRESERVE_ZERO_FRACTION``. You can use the serialization -context to pass in these options using the key ``json_encode_options`` or -``json_decode_options`` respectively:: - - $this->serializer->serialize($data, 'json', ['json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION]); - -These are the options available: - -=============================== =========================================================================================================== ================================ -Option Description Default -=============================== ========================================================================================================== ================================ -``json_decode_associative`` If set to true returns the result as an array, returns a nested ``stdClass`` hierarchy otherwise. ``false`` -``json_decode_detailed_errors`` If set to true, exceptions thrown on parsing of JSON are more specific. Requires `seld/jsonlint`_ package. ``false`` -``json_decode_options`` `$flags`_ passed to :phpfunction:`json_decode` function. ``0`` -``json_encode_options`` `$flags`_ passed to :phpfunction:`json_encode` function. ``\JSON_PRESERVE_ZERO_FRACTION`` -``json_decode_recursion_depth`` Sets maximum recursion depth. ``512`` -=============================== ========================================================================================================== ================================ - -.. versionadded:: 6.4 - - The support of ``json_decode_detailed_errors`` was introduced in Symfony 6.4. - -The ``CsvEncoder`` -~~~~~~~~~~~~~~~~~~ - -The ``CsvEncoder`` encodes to and decodes from CSV. - -The ``CsvEncoder`` Context Options -.................................. - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the CsvEncoder an associative array:: - - $csvEncoder->encode($array, 'csv', $context); - -These are the options available: - -======================= ===================================================== ========================== -Option Description Default -======================= ===================================================== ========================== -``csv_delimiter`` Sets the field delimiter separating values (one ``,`` - character only) -``csv_enclosure`` Sets the field enclosure (one character only) ``"`` -``csv_end_of_line`` Sets the character(s) used to mark the end of each ``\n`` - line in the CSV file -``csv_escape_char`` Sets the escape character (at most one character) empty string -``csv_key_separator`` Sets the separator for array's keys during its ``.`` - flattening -``csv_headers`` Sets the order of the header and data columns - E.g.: if ``$data = ['c' => 3, 'a' => 1, 'b' => 2]`` - and ``$options = ['csv_headers' => ['a', 'b', 'c']]`` - then ``serialize($data, 'csv', $options)`` returns - ``a,b,c\n1,2,3`` ``[]``, inferred from input data's keys -``csv_escape_formulas`` Escapes fields containing formulas by prepending them ``false`` - with a ``\t`` character -``as_collection`` Always returns results as a collection, even if only ``true`` - one line is decoded. -``no_headers`` Setting to ``false`` will use first row as headers. ``false`` - ``true`` generate numeric headers. -``output_utf8_bom`` Outputs special `UTF-8 BOM`_ along with encoded data ``false`` -======================= ===================================================== ========================== - -The ``XmlEncoder`` -~~~~~~~~~~~~~~~~~~ - -This encoder transforms arrays into XML and vice versa. - -For example, take an object normalized as following:: - - ['foo' => [1, 2], 'bar' => true]; - -The ``XmlEncoder`` will encode this object like that: - -.. code-block:: xml - - - - 1 - 2 - 1 - - -The special ``#`` key can be used to define the data of a node:: - - ['foo' => ['@bar' => 'value', '#' => 'baz']]; - - // is encoded as follows: - // - // - // - // baz - // - // - -Furthermore, keys beginning with ``@`` will be considered attributes, and -the key ``#comment`` can be used for encoding XML comments:: - - $encoder = new XmlEncoder(); - $encoder->encode([ - 'foo' => ['@bar' => 'value'], - 'qux' => ['#comment' => 'A comment'], - ], 'xml'); - // will return: - // - // - // - // - // - -You can pass the context key ``as_collection`` in order to have the results -always as a collection. - -.. note:: - - You may need to add some attributes on the root node:: - - $encoder = new XmlEncoder(); - $encoder->encode([ - '@attribute1' => 'foo', - '@attribute2' => 'bar', - '#' => ['foo' => ['@bar' => 'value', '#' => 'baz']] - ], 'xml'); - - // will return: - // - // - // baz - // - -.. tip:: - - XML comments are ignored by default when decoding contents, but this - behavior can be changed with the optional context key ``XmlEncoder::DECODER_IGNORED_NODE_TYPES``. - - Data with ``#comment`` keys are encoded to XML comments by default. This can be - changed by adding the ``\XML_COMMENT_NODE`` option to the ``XmlEncoder::ENCODER_IGNORED_NODE_TYPES`` - key of the ``$defaultContext`` of the ``XmlEncoder`` constructor or - directly to the ``$context`` argument of the ``encode()`` method:: - - $xmlEncoder->encode($array, 'xml', [XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_COMMENT_NODE]]); - -The ``XmlEncoder`` Context Options -.................................. - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the XmlEncoder an associative array:: - - $xmlEncoder->encode($array, 'xml', $context); - -These are the options available: - -============================== ================================================= ========================== -Option Description Default -============================== ================================================= ========================== -``xml_format_output`` If set to true, formats the generated XML with ``false`` - line breaks and indentation -``xml_version`` Sets the XML version attribute ``1.0`` -``xml_encoding`` Sets the XML encoding attribute ``utf-8`` -``xml_standalone`` Adds standalone attribute in the generated XML ``true`` -``xml_type_cast_attributes`` This provides the ability to forget the attribute ``true`` - type casting -``xml_root_node_name`` Sets the root node name ``response`` -``as_collection`` Always returns results as a collection, even if ``false`` - only one line is decoded -``decoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[\XML_PI_NODE, \XML_COMMENT_NODE]`` - to be ignored while decoding -``encoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[]`` - to be ignored while encoding -``load_options`` XML loading `options with libxml`_ ``\LIBXML_NONET | \LIBXML_NOBLANKS`` -``save_options`` XML saving `options with libxml`_ ``0`` -``remove_empty_tags`` If set to true, removes all empty tags in the ``false`` - generated XML -``cdata_wrapping`` If set to false, will not wrap any value ``true`` - containing one of the following characters ( - ``<``, ``>``, ``&``) in `a CDATA section`_ like - following: ```` -============================== ================================================= ========================== - -.. versionadded:: 6.4 - - The `cdata_wrapping` option was introduced in Symfony 6.4. - -Example with custom ``context``:: - - use Symfony\Component\Serializer\Encoder\XmlEncoder; - - // create encoder with specified options as new default settings - $xmlEncoder = new XmlEncoder(['xml_format_output' => true]); - - $data = [ - 'id' => 'IDHNQIItNyQ', - 'date' => '2019-10-24', - ]; - - // encode with default context - $xmlEncoder->encode($data, 'xml'); - // outputs: - // - // - // IDHNQIItNyQ - // 2019-10-24 - // - - // encode with modified context - $xmlEncoder->encode($data, 'xml', [ - 'xml_root_node_name' => 'track', - 'encoder_ignored_node_types' => [ - \XML_PI_NODE, // removes XML declaration (the leading xml tag) - ], - ]); - // outputs: - // - // IDHNQIItNyQ - // 2019-10-24 - // - -The ``YamlEncoder`` -~~~~~~~~~~~~~~~~~~~ - -This encoder requires the :doc:`Yaml Component ` and -transforms from and to Yaml. - -The ``YamlEncoder`` Context Options -................................... - -The ``encode()`` method, like other encoder, uses ``context`` to set -configuration options for the YamlEncoder an associative array:: - - $yamlEncoder->encode($array, 'yaml', $context); - -These are the options available: - -=============== ======================================================== ========================== -Option Description Default -=============== ======================================================== ========================== -``yaml_inline`` The level where you switch to inline YAML ``0`` -``yaml_indent`` The level of indentation (used internally) ``0`` -``yaml_flags`` A bit field of ``Yaml::DUMP_*`` / ``PARSE_*`` constants ``0`` - to customize the encoding / decoding YAML string -=============== ======================================================== ========================== - -.. _component-serializer-context-builders: - -Context Builders ----------------- - -Instead of passing plain PHP arrays to the :ref:`serialization context `, -you can use "context builders" to define the context using a fluent interface:: - - use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; - use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder; - - $initialContext = [ - 'custom_key' => 'custom_value', - ]; - - $contextBuilder = (new ObjectNormalizerContextBuilder()) - ->withContext($initialContext) - ->withGroups(['group1', 'group2']); - - $contextBuilder = (new CsvEncoderContextBuilder()) - ->withContext($contextBuilder) - ->withDelimiter(';'); - - $serializer->serialize($something, 'csv', $contextBuilder->toArray()); - -.. versionadded:: 6.1 - - Context builders were introduced in Symfony 6.1. - -.. note:: - - The Serializer component provides a context builder - for each :ref:`normalizer ` - and :ref:`encoder `. - - You can also :doc:`create custom context builders ` - to deal with your context values. - -Skipping ``null`` Values ------------------------- - -By default, the Serializer will preserve properties containing a ``null`` value. -You can change this behavior by setting the ``AbstractObjectNormalizer::SKIP_NULL_VALUES`` context option -to ``true``:: - - $dummy = new class { - public ?string $foo = null; - public string $bar = 'notNull'; - }; - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->normalize($dummy, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]); - // ['bar' => 'notNull'] - -Require all Properties ----------------------- - -By default, the Serializer will add ``null`` to nullable properties when the parameters for those are not provided. -You can change this behavior by setting the ``AbstractNormalizer::REQUIRE_ALL_PROPERTIES`` context option -to ``true``:: - - class Dummy - { - public function __construct( - public string $foo, - public ?string $bar, - ) { - } - } - - $data = ['foo' => 'notNull']; - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->denormalize($data, Dummy::class, 'json', [AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true]); - // throws Symfony\Component\Serializer\Exception\MissingConstructorArgumentException - -.. versionadded:: 6.3 - - The ``AbstractNormalizer::PREVENT_NULLABLE_FALLBACK`` context option - was introduced in Symfony 6.3. - -Skipping Uninitialized Properties ---------------------------------- - -In PHP, typed properties have an ``uninitialized`` state which is different -from the default ``null`` of untyped properties. When you try to access a typed -property before giving it an explicit value, you get an error. - -To avoid the Serializer throwing an error when serializing or normalizing an -object with uninitialized properties, by default the object normalizer catches -these errors and ignores such properties. - -You can disable this behavior by setting the ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` -context option to ``false``:: - - class Dummy { - public string $foo = 'initialized'; - public string $bar; // uninitialized - } - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->normalize(new Dummy(), 'json', [AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false]); - // throws Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException as normalizer cannot read uninitialized properties - -.. note:: - - Calling ``PropertyNormalizer::normalize`` or ``GetSetMethodNormalizer::normalize`` - with ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context option set - to ``false`` will throw an ``\Error`` instance if the given object has uninitialized - properties as the normalizer cannot read them (directly or via getter/isser methods). - -.. _component-serializer-handling-circular-references: - -Collecting Type Errors While Denormalizing ------------------------------------------- - -When denormalizing a payload to an object with typed properties, you'll get an -exception if the payload contains properties that don't have the same type as -the object. - -In those situations, use the ``COLLECT_DENORMALIZATION_ERRORS`` option to -collect all exceptions at once, and to get the object partially denormalized:: - - try { - $dto = $serializer->deserialize($request->getContent(), MyDto::class, 'json', [ - DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, - ]); - } catch (PartialDenormalizationException $e) { - $violations = new ConstraintViolationList(); - /** @var NotNormalizableValueException $exception */ - foreach ($e->getErrors() as $exception) { - $message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType()); - $parameters = []; - if ($exception->canUseMessageForUser()) { - $parameters['hint'] = $exception->getMessage(); - } - $violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null)); - } - - return $this->json($violations, 400); - } - -Handling Circular References ----------------------------- - -Circular references are common when dealing with entity relations:: - - class Organization - { - private string $name; - private array $members; - - public function setName($name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function setMembers(array $members): void - { - $this->members = $members; - } - - public function getMembers(): array - { - return $this->members; - } - } - - class Member - { - private string $name; - private Organization $organization; - - public function setName(string $name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function setOrganization(Organization $organization): void - { - $this->organization = $organization; - } - - public function getOrganization(): Organization - { - return $this->organization; - } - } - -To avoid infinite loops, :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` -or :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` -throw a :class:`Symfony\\Component\\Serializer\\Exception\\CircularReferenceException` -when such a case is encountered:: - - $member = new Member(); - $member->setName('Kévin'); - - $organization = new Organization(); - $organization->setName('Les-Tilleuls.coop'); - $organization->setMembers([$member]); - - $member->setOrganization($organization); - - echo $serializer->serialize($organization, 'json'); // Throws a CircularReferenceException - -The key ``circular_reference_limit`` in the default context sets the number of -times it will serialize the same object before considering it a circular -reference. The default value is ``1``. - -Instead of throwing an exception, circular references can also be handled -by custom callables. This is especially useful when serializing entities -having unique identifiers:: - - $encoder = new JsonEncoder(); - $defaultContext = [ - AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (object $object, ?string $format, array $context): string { - return $object->getName(); - }, - ]; - $normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer], [$encoder]); - var_dump($serializer->serialize($org, 'json')); - // {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]} - -.. _serializer_handling-serialization-depth: - -Handling Serialization Depth ----------------------------- - -The Serializer component is able to detect and limit the serialization depth. -It is especially useful when serializing large trees. Assume the following data -structure:: - - namespace Acme; - - class MyObj - { - public string $foo; - - /** - * @var self - */ - public MyObj $child; - } - - $level1 = new MyObj(); - $level1->foo = 'level1'; - - $level2 = new MyObj(); - $level2->foo = 'level2'; - $level1->child = $level2; - - $level3 = new MyObj(); - $level3->foo = 'level3'; - $level2->child = $level3; - -The serializer can be configured to set a maximum depth for a given property. -Here, we set it to 2 for the ``$child`` property: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace Acme; - - use Symfony\Component\Serializer\Annotation\MaxDepth; - - class MyObj - { - #[MaxDepth(2)] - public MyObj $child; - - // ... - } - - .. code-block:: yaml - - Acme\MyObj: - attributes: - child: - max_depth: 2 - - .. code-block:: xml - - - - - - - - -The metadata loader corresponding to the chosen format must be configured in -order to use this feature. It is done automatically when using the Serializer component -in a Symfony application. When using the standalone component, refer to -:ref:`the groups documentation ` to -learn how to do that. - -The check is only done if the ``AbstractObjectNormalizer::ENABLE_MAX_DEPTH`` key of the serializer context -is set to ``true``. In the following example, the third level is not serialized -because it is deeper than the configured maximum depth of 2:: - - $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]); - /* - $result = [ - 'foo' => 'level1', - 'child' => [ - 'foo' => 'level2', - 'child' => [ - 'child' => null, - ], - ], - ]; - */ - -Instead of throwing an exception, a custom callable can be executed when the -maximum depth is reached. This is especially useful when serializing entities -having unique identifiers:: - - use Symfony\Component\Serializer\Annotation\MaxDepth; - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; - use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class Foo - { - public int $id; - - #[MaxDepth(1)] - public MyObj $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 AttributeLoader()); - - // all callback parameters are optional (you can omit the ones you don't use) - $maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, ?string $format = null, array $context = []): string { - return '/foos/'.$innerObject->id; - }; - - $defaultContext = [ - AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler, - ]; - $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer]); - - $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]); - /* - $result = [ - 'id' => 1, - 'child' => [ - 'id' => 2, - 'child' => '/foos/3', - ], - ]; - */ - -Handling Arrays ---------------- - -The Serializer component is capable of handling arrays of objects as well. -Serializing arrays works just like serializing a single object:: - - use Acme\Person; - - $person1 = new Person(); - $person1->setName('foo'); - $person1->setAge(99); - $person1->setSportsman(false); - - $person2 = new Person(); - $person2->setName('bar'); - $person2->setAge(33); - $person2->setSportsman(true); - - $persons = [$person1, $person2]; - $data = $serializer->serialize($persons, 'json'); - - // $data contains [{"name":"foo","age":99,"sportsman":false},{"name":"bar","age":33,"sportsman":true}] - -If you want to deserialize such a structure, you need to add the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer` -to the set of normalizers. By appending ``[]`` to the type parameter of the -:method:`Symfony\\Component\\Serializer\\Serializer::deserialize` method, -you indicate that you're expecting an array instead of a single object:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - use Symfony\Component\Serializer\Serializer; - - $serializer = new Serializer( - [new GetSetMethodNormalizer(), new ArrayDenormalizer()], - [new JsonEncoder()] - ); - - $data = ...; // The serialized data from the previous example - $persons = $serializer->deserialize($data, 'Acme\Person[]', 'json'); - -Handling Constructor Arguments ------------------------------- - -If the class constructor defines arguments, as usually happens with -`Value Objects`_, the serializer won't be able to create the object if some -arguments are missing. In those cases, use the ``default_constructor_arguments`` -context option:: - - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class MyObj - { - public function __construct( - private string $foo, - private string $bar, - ) { - } - } - - $normalizer = new ObjectNormalizer(); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->denormalize( - ['foo' => 'Hello'], - 'MyObj', - null, - [AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [ - 'MyObj' => ['foo' => '', 'bar' => ''], - ]] - ); - // $data = new MyObj('Hello', ''); - -Recursive Denormalization and Type Safety ------------------------------------------ - -The Serializer component can use the :doc:`PropertyInfo Component ` to denormalize -complex types (objects). The type of the class' property will be guessed using the provided -extractor and used to recursively denormalize the inner data. - -When using this component in a Symfony application, all normalizers are automatically configured to use the registered extractors. -When using the component standalone, an implementation of :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`, -(usually an instance of :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`) must be passed as the 4th -parameter of the ``ObjectNormalizer``:: - - namespace Acme; - - use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; - use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class ObjectOuter - { - private ObjectInner $inner; - private \DateTimeInterface $date; - - public function getInner(): ObjectInner - { - return $this->inner; - } - - public function setInner(ObjectInner $inner): void - { - $this->inner = $inner; - } - - public function getDate(): \DateTimeInterface - { - return $this->date; - } - - public function setDate(\DateTimeInterface $date): void - { - $this->date = $date; - } - } - - class ObjectInner - { - public string $foo; - public string $bar; - } - - $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); - $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]); - - $obj = $serializer->denormalize( - ['inner' => ['foo' => 'foo', 'bar' => 'bar'], 'date' => '1988/01/21'], - 'Acme\ObjectOuter' - ); - - dump($obj->getInner()->foo); // 'foo' - dump($obj->getInner()->bar); // 'bar' - dump($obj->getDate()->format('Y-m-d')); // '1988-01-21' - -When a ``PropertyTypeExtractor`` is available, the normalizer will also check that the data to denormalize -matches the type of the property (even for primitive types). For instance, if a ``string`` is provided, but -the type of the property is ``int``, an :class:`Symfony\\Component\\Serializer\\Exception\\UnexpectedValueException` -will be thrown. The type enforcement of the properties can be disabled by setting -the serializer context option ``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT`` -to ``true``. - -.. _serializer_interfaces-and-abstract-classes: - -Serializing Interfaces and Abstract Classes -------------------------------------------- - -When dealing with objects that are fairly similar or share properties, you may -use interfaces or abstract classes. The Serializer component allows you to -serialize and deserialize these objects using a *"discriminator class mapping"*. - -The discriminator is the field (in the serialized string) used to differentiate -between the possible objects. In practice, when using the Serializer component, -pass a :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorResolverInterface` -implementation to the :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`. - -The Serializer component provides an implementation of ``ClassDiscriminatorResolverInterface`` -called :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorFromClassMetadata` -which uses the class metadata factory and a mapping configuration to serialize -and deserialize objects of the correct class. - -When using this component inside a Symfony application and the class metadata factory is enabled -as explained in the :ref:`Attributes Groups section `, -this is already set up and you only need to provide the configuration. Otherwise:: - - // ... - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; - use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); - - $serializer = new Serializer( - [new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator)], - ['json' => new JsonEncoder()] - ); - -Now configure your discriminator class mapping. Consider an application that -defines an abstract ``CodeRepository`` class extended by ``GitHubCodeRepository`` -and ``BitBucketCodeRepository`` classes: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App; - - use App\BitBucketCodeRepository; - use App\GitHubCodeRepository; - use Symfony\Component\Serializer\Annotation\DiscriminatorMap; - - #[DiscriminatorMap(typeProperty: 'type', mapping: [ - 'github' => GitHubCodeRepository::class, - 'bitbucket' => BitBucketCodeRepository::class, - ])] - abstract class CodeRepository - { - // ... - } - - .. code-block:: yaml - - App\CodeRepository: - discriminator_map: - type_property: type - mapping: - github: 'App\GitHubCodeRepository' - bitbucket: 'App\BitBucketCodeRepository' - - .. code-block:: xml - - - - - - - - - - - -.. note:: - - The values of the ``mapping`` array option must be strings. - Otherwise, they will be cast into strings automatically. - -Once configured, the serializer uses the mapping to pick the correct class:: - - $serialized = $serializer->serialize(new GitHubCodeRepository(), 'json'); - // {"type": "github"} - - $repository = $serializer->deserialize($serialized, CodeRepository::class, 'json'); - // instanceof GitHubCodeRepository - -Learn more ----------- - -.. toctree:: - :maxdepth: 1 - :glob: - - /serializer - -.. seealso:: - - Normalizers for the Symfony Serializer Component supporting popular web API formats - (JSON-LD, GraphQL, OpenAPI, HAL, JSON:API) are available as part of the `API Platform`_ project. - -.. seealso:: - - A popular alternative to the Symfony Serializer component is the third-party - library, `JMS serializer`_ (versions before ``v1.12.0`` were released under - the Apache license, so incompatible with GPLv2 projects). - -.. _`PSR-1 standard`: https://www.php-fig.org/psr/psr-1/ -.. _`JMS serializer`: https://github.com/schmittjoh/serializer -.. _RFC3339: https://tools.ietf.org/html/rfc3339#section-5.8 -.. _`options with libxml`: https://www.php.net/manual/en/libxml.constants.php -.. _`DOM XML_* constants`: https://www.php.net/manual/en/dom.constants.php -.. _JSON: https://www.json.org/json-en.html -.. _XML: https://www.w3.org/XML/ -.. _YAML: https://yaml.org/ -.. _CSV: https://tools.ietf.org/html/rfc4180 -.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807 -.. _`UTF-8 BOM`: https://en.wikipedia.org/wiki/Byte_order_mark -.. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object -.. _`API Platform`: https://api-platform.com -.. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php -.. _`RFC 4122`: https://tools.ietf.org/html/rfc4122 -.. _`PHP reflection`: https://php.net/manual/en/book.reflection.php -.. _`data URI`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs -.. _seld/jsonlint: https://github.com/Seldaek/jsonlint -.. _$flags: https://www.php.net/manual/en/json.constants.php -.. _`a CDATA section`: https://en.wikipedia.org/wiki/CDATA diff --git a/reference/attributes.rst b/reference/attributes.rst index 4f784588e23..feadec70d3c 100644 --- a/reference/attributes.rst +++ b/reference/attributes.rst @@ -99,16 +99,18 @@ Security * :ref:`CurrentUser ` * :ref:`IsGranted ` +.. _reference-attributes-serializer: + Serializer ~~~~~~~~~~ -* :ref:`Context ` +* :ref:`Context ` * :ref:`DiscriminatorMap ` -* :ref:`Groups ` +* :ref:`Groups ` * :ref:`Ignore ` * :ref:`MaxDepth ` -* :ref:`SerializedName ` -* :ref:`SerializedPath ` +* :ref:`SerializedName ` +* :ref:`SerializedPath ` Twig ~~~~ diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index e194ca2afa5..8e1f6af81fa 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -2981,7 +2981,7 @@ enable_annotations **type**: ``boolean`` **default**: ``true`` -If this option is enabled, serialization groups can be defined using annotations or attributes. +Enables support for annotations or attributes in the serializer component. .. deprecated:: 6.4 @@ -2993,11 +2993,11 @@ enable_attributes **type**: ``boolean`` **default**: ``true`` -If this option is enabled, serialization groups can be defined using `PHP attributes`_. +Enables support for `PHP attributes`_ in the serializer component. .. seealso:: - For more information, see :ref:`serializer-using-serialization-groups-attributes`. + See :ref:`the reference ` for a list of supported annotations. .. _reference-serializer-name_converter: @@ -3013,8 +3013,7 @@ value. .. seealso:: - For more information, see - :ref:`component-serializer-converting-property-names-when-serializing-and-deserializing`. + For more information, see :ref:`serializer-name-conversion`. .. _reference-serializer-circular_reference_handler: diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index 4a51940b96e..a34cfe58f0c 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -713,6 +713,8 @@ project's root directory: If the given file path is out of the project directory, a ``null`` value will be returned. +.. _reference-twig-filter-serialize: + serialize ~~~~~~~~~ diff --git a/serializer.rst b/serializer.rst index 2900a49ba4e..4efdbf2dd45 100644 --- a/serializer.rst +++ b/serializer.rst @@ -1,10 +1,17 @@ How to Use the Serializer ========================= -Symfony provides a serializer to serialize/deserialize to and from objects and -different formats (e.g. JSON or XML). Before using it, read the -:doc:`Serializer component docs ` to get familiar with -its philosophy and the normalizers and encoders terminology. +Symfony provides a serializer to transform data structures from one format +to PHP objects and the other way around. + +This is most commonly used when building an API or communicating with third +party APIs. The serializer can transform an incoming JSON request payload +to a PHP object that is consumed by your application. Then, when generating +the response, you can use the serializer to transform the PHP objects back +to a JSON response. + +It can also be used to for instance load CSV configuration data as PHP +objects, or even to transform between formats (e.g. YAML to XML). .. _activating_the_serializer: @@ -12,287 +19,387 @@ Installation ------------ In applications using :ref:`Symfony Flex `, run this command to -install the ``serializer`` :ref:`Symfony pack ` before using it: +install the serializer :ref:`Symfony pack ` before using it: .. code-block:: terminal $ composer require symfony/serializer-pack -Using the Serializer Service ----------------------------- +.. note:: + + The serializer pack also installs some commonly used optional + dependencies of the Serializer component. When using this component + outside the Symfony framework, you might want to start with the + ``symfony/serializer`` package and install optional dependencies if you + need them. + +.. seealso:: -Once enabled, the serializer service can be injected in any service where -you need it or it can be used in a controller:: + A popular alternative to the Symfony Serializer component is the third-party + library, `JMS serializer`_. - // src/Controller/DefaultController.php - namespace App\Controller; +Serializing an Object +--------------------- - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\Serializer\SerializerInterface; +For this example, assume the following class exists in your project:: - class DefaultController extends AbstractController + // src/Model/Person.php + namespace App\Model; + + class Person { - public function index(SerializerInterface $serializer): Response + public function __construct( + private int $age, + private string $name, + private bool $sportsperson + ) { + } + + public function getAge(): int { - // keep reading for usage examples + return $this->age; } - } -Or you can use the ``serialize`` Twig filter in a template: + public function getName(): string + { + return $this->name; + } -.. code-block:: twig + public function isSportsperson(): bool + { + return $this->sportsperson; + } + } - {{ object|serialize(format = 'json') }} +If you want to transform objects of this type into a JSON structure (e.g. +to send them via an API response), get the ``serializer`` service by using +the :class:`Symfony\\Component\\Serializer\\SerializerInterface` parameter type: -See the :doc:`twig reference ` for -more information. +.. configuration-block:: -Adding Normalizers and Encoders -------------------------------- + .. code-block:: php-symfony -Once enabled, the ``serializer`` service will be available in the container. -It comes with a set of useful :ref:`encoders ` -and :ref:`normalizers `. - -Encoders supporting the following formats are enabled: - -* JSON: :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder` -* XML: :class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder` -* CSV: :class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder` -* YAML: :class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder` - -As well as the following normalizers: - -* :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer` -* :class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer` - -Other :ref:`built-in normalizers ` and -custom normalizers and/or encoders can also be loaded by tagging them as -:ref:`serializer.normalizer ` and -:ref:`serializer.encoder `. It's also -possible to set the priority of the tag in order to decide the matching order. + // src/Controller/PersonController.php + namespace App\Controller; -.. danger:: + use App\Model\Person; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\JsonResponse; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Serializer\SerializerInterface; - Always make sure to load the ``DateTimeNormalizer`` when serializing the - ``DateTime`` or ``DateTimeImmutable`` classes to avoid excessive memory - usage and exposing internal details. + class PersonController extends AbstractController + { + public function index(SerializerInterface $serializer): Response + { + $person = new Person('Jane Doe', 39, false); -.. _serializer_serializer-context: + $jsonContent = $serializer->serialize($person, 'json'); + // $jsonContent contains {"name":"Jane Doe","age":39,"sportsperson":false} -Serializer Context ------------------- + return JsonResponse::fromJsonString($jsonContent); + } + } -The serializer can define a context to control the (de)serialization of -resources. This context is passed to all normalizers. For example: + .. code-block:: php-standalone -* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` uses - ``datetime_format`` key as date time format; -* :class:`Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer` - uses ``preserve_empty_objects`` to represent empty objects as ``{}`` instead - of ``[]`` in JSON. -* :class:`Symfony\\Component\\Serializer\\Serializer` - uses ``empty_array_as_object`` to represent empty arrays as ``{}`` instead - of ``[]`` in JSON. + use App\Model\Person; + use Symfony\Component\Serializer\Encoder\JsonEncoder; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; -You can pass the context as follows:: + $encoders = [new JsonEncoder()]; + $normalizers = [new ObjectNormalizer()]; + $serializer = new Serializer($normalizers, $encoders); - $serializer->serialize($something, 'json', [ - DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i:s', - ]); + $person = new Person('Jane Done', 39, false); - $serializer->deserialize($someJson, Something::class, 'json', [ - DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i:s', - ]); + $jsonContent = $serializer->serialize($person, 'json'); + // $jsonContent contains {"name":"Jane Doe","age":39,"sportsperson":false} -You can also configure the default context through the framework -configuration: +The first parameter of the :method:`Symfony\\Component\\Serializer\\Serializer::serialize` +is the object to be serialized and the second is used to choose the proper +encoder (i.e. format), in this case the :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`. -.. configuration-block:: +.. tip:: - .. code-block:: yaml + When your controller class extends ``AbstractController`` (like in the + example above), you can simplify your controller by using the + :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::json` + method to create a JSON response from an object using the Serializer:: - # config/packages/framework.yaml - framework: - # ... - serializer: - default_context: - enable_max_depth: true - yaml_indentation: 2 + class PersonController extends AbstractController + { + public function index(): Response + { + $person = new Person('Jane Doe', 39, false); - .. code-block:: xml + // when the Serializer is not available, this will use json_encode() + return $this->json($person); + } + } - - - - - - - +Using the Serializer in Twig Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - .. code-block:: php +You can also serialize objects in any Twig template using the ``serialize`` +filter: - // config/packages/framework.php - use Symfony\Component\Serializer\Encoder\YamlEncoder; - use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; - use Symfony\Config\FrameworkConfig; +.. code-block:: twig - return static function (FrameworkConfig $framework): void { - $framework->serializer() - ->defaultContext([ - AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true, - YamlEncoder::YAML_INDENTATION => 2, - ]) - ; - }; + {{ person|serialize(format = 'json') }} -.. versionadded:: 6.2 +See the :ref:`twig reference ` for more +information. - The option to configure YAML indentation was introduced in Symfony 6.2. +Deserializing an Object +----------------------- -You can also specify the context on a per-property basis:: +APIs often also need to convert a formatted request body (e.g. JSON) to a +PHP object. This process is called *deserialization* (also known as "hydration"): .. configuration-block:: - .. code-block:: php-attributes + .. code-block:: php-symfony - namespace App\Model; + // src/Controller/PersonController.php + namespace App\Controller; - use Symfony\Component\Serializer\Annotation\Context; - use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; + // ... + use Symfony\Component\HttpFoundation\Exception\BadRequestException; + use Symfony\Component\HttpFoundation\Request; - class Person + class PersonController extends AbstractController { - #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])] - public \DateTimeInterface $createdAt; - // ... + + public function create(Request $request, SerializerInterface $serializer): Response + { + if ('json' !== $request->getContentTypeFormat()) { + throw new BadRequestException('Unsupported content format'); + } + + $jsonData = $request->getContent(); + $person = $serializer->deserialize($jsonData, Person::class, 'json'); + + // ... do something with $person and return a response + } } - .. code-block:: yaml + .. code-block:: php-standalone - # config/serializer/custom_config.yaml - App\Model\Person: - attributes: - createdAt: - contexts: - - { context: { datetime_format: 'Y-m-d' } } + use App\Model\Person; + use Symfony\Component\Serializer\Encoder\JsonEncoder; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; - .. code-block:: xml + // ... + $jsonData = ...; // fetch JSON from the request + $person = $serializer->deserialize($jsonData, Person::class, 'json'); - - - - - - - Y-m-d - - - - +In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize` +needs three parameters: -Use the options to specify context specific to normalization or denormalization:: +#. The data to be decoded +#. The name of the class this information will be decoded to +#. The name of the encoder used to convert the data to an array (i.e. the + input format) - namespace App\Model; +When sending a request to this controller (e.g. +``{"first_name":"John Doe","age":54,"sportsperson":true}``), the serializer +will create a new instance of ``Person`` and sets the properties to the +values from the given JSON. - use Symfony\Component\Serializer\Annotation\Context; - use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +.. note:: - class Person - { - #[Context( - normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'], - denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => '!Y-m-d'], // To prevent to have the time from the moment of denormalization - )] - public \DateTimeInterface $createdAt; + By default, additional attributes that are not mapped to the + denormalized object will be ignored by the Serializer component. For + instance, if a request to the above controller contains ``{..., "city": "Paris"}``, + the ``city`` field will be ignored. You can also throw an exception in + these cases using the :ref:`serializer context ` + you'll learn about later. - // ... - } +.. seealso:: -You can also restrict the usage of a context to some groups:: + You can also deserialize data into an existing object instance (e.g. + when updating data). See :ref:`Deserializing in an Existing Object `. - namespace App\Model; +.. _serializer-process: - use Symfony\Component\Serializer\Annotation\Context; - use Symfony\Component\Serializer\Annotation\Groups; - use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +The Serialization Process: Normalizers and Encoders +--------------------------------------------------- - class Person - { - #[Groups(['extended'])] - #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])] - #[Context( - context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED], - groups: ['extended'], - )] - public \DateTimeInterface $createdAt; +The serializer uses a two-step process when (de)serializing objects: - // ... - } +.. raw:: html -The attribute can be repeated as much as needed on a single property. -Context without group is always applied first. Then context for the matching -groups are merged in the provided order. + -If you repeat the same context in multiple properties, consider using the -``#[Context]`` attribute on your class to apply that context configuration to -all the properties of the class:: +In both directions, data is always first converted to an array. This splits +the process in two seperate responsibilities: - namespace App\Model; +Normalizers + These classes convert **objects** into **arrays** and vice versa. They + do the heavy lifting of finding out which class properties to + serialize, what value they hold and what name they should have. +Encoders + Encoders convert **arrays** into a specific **format** and the other + way around. Each encoder knows exactly how to parse and generate a + specific format, for instance JSON or XML. - use Symfony\Component\Serializer\Annotation\Context; - use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +Internally, the ``Serializer`` class uses a sorted list of normalizers and +one encoder for the specific format when (de)serializing an object. + +There are several normalizers configured in the default ``serializer`` +service. The most important normalizer is the +:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`. This +normalizer uses reflection and the :doc:`PropertyAccess component ` +to transform between any object and an array. You'll learn more about +:ref:`this and other normalizers ` later. + +The default serializer is also configured with some encoders, covering the +common formats used by HTTP applications: + +* :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder` +* :class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder` +* :class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder` +* :class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder` + +Read more about these encoders and their configuration in +:doc:`/serializer/encoders`. + +.. tip:: + + The `API Platform`_ project provides encoders for more advanced + formats: + + * `JSON-LD`_ along with the `Hydra Core Vocabulary`_ + * `OpenAPI`_ v2 (formerly Swagger) and v3 + * `GraphQL`_ + * `JSON:API`_ + * `HAL`_ + +.. _serializer-context: + +Serializer Context +~~~~~~~~~~~~~~~~~~ + +The serializer, and its normalizers and encoders, are configured through +the *serializer context*. This context can be configured in multiple +places: + +* `Globally through the framework configuration `_ +* `While serializing/deserializing `_ +* `For a specific property `_ + +You can use all three options at the same time. When the same setting is +configured in multiple places, the latter in the list above will override +the previous one (e.g. the setting on a specific property overrides the one +configured globally). + +.. _serializer-default-context: + +Configure a Default Context +........................... + +You can configure a default context in the framework configuration, for +instance to disallow extra fields while deserializing: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/serializer.yaml + framework: + serializer: + default_context: + allow_extra_attributes: false + + .. code-block:: xml + + + + + + + + + false + + + + + + .. code-block:: php + + // config/packages/serializer.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $framework->serializer() + ->defaultContext('', [ + 'allow_extra_attributes' => false, + ]) + ; + }; + + .. code-block:: php-standalone + + use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])] - #[Context( - context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED], - groups: ['extended'], - )] - class Person - { // ... - } + $normalizers = [ + new ObjectNormalizer(null, null, null, null, null, null, [ + 'allow_extra_attributes' => false, + ]), + ]; + $serializer = new Serializer($normalizers, $encoders); + +Pass Context while Serializing/Deserializing +............................................ + +You can also configure the context for a single call to +``serialize()``/``deserialize()``. For instance, you can skip +properties with a ``null`` value only for one serialize call:: + + use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; -.. versionadded:: 6.4 + // ... + $serializer->serialize($person, 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true + ]); - The ``#[Context]`` attribute was introduced in Symfony 6.4. + // next calls to serialize() will NOT skip null values .. _serializer-using-context-builders: Using Context Builders ----------------------- +"""""""""""""""""""""" .. versionadded:: 6.1 Context builders were introduced in Symfony 6.1. -To define the (de)serialization context, you can use "context builders", which -are objects that help you to create that context by providing autocompletion, -validation, and documentation:: +You can use "context builders" to help define the (de)serialization +context. Context builders are PHP objects that provide autocompletion, +validation, and documentation of context options:: use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder; - $contextBuilder = (new DateTimeNormalizerContextBuilder())->withFormat('Y-m-d H:i:s'); + $contextBuilder = (new DateTimeNormalizerContextBuilder()) + ->withFormat('Y-m-d H:i:s'); $serializer->serialize($something, 'json', $contextBuilder->toArray()); -Each normalizer/encoder has its related :ref:`context builder `. -To create a more complex (de)serialization context, you can chain them using the +Each normalizer/encoder has its related context builder. To create a more +complex (de)serialization context, you can chain them using the ``withContext()`` method:: use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; @@ -312,129 +419,111 @@ To create a more complex (de)serialization context, you can chain them using the $serializer->serialize($something, 'csv', $contextBuilder->toArray()); -You can also :doc:`create your context builders ` -to have autocompletion, validation, and documentation for your custom context values. - -.. _serializer-using-serialization-groups-attributes: - -Using Serialization Groups Attributes -------------------------------------- - -You can add :ref:`#[Groups] attributes ` -to your class properties:: - - // src/Entity/Product.php - namespace App\Entity; - - use Doctrine\ORM\Mapping as ORM; - use Symfony\Component\Serializer\Annotation\Groups; - - #[ORM\Entity] - class Product - { - #[ORM\Id] - #[ORM\GeneratedValue] - #[ORM\Column(type: 'integer')] - #[Groups(['show_product', 'list_product'])] - private int $id; - - #[ORM\Column(type: 'string', length: 255)] - #[Groups(['show_product', 'list_product'])] - private string $name; - - #[ORM\Column(type: 'text')] - #[Groups(['show_product'])] - private string $description; - } - -You can also use the ``#[Groups]`` attribute on class level:: +.. seealso:: - #[ORM\Entity] - #[Groups(['show_product'])] - class Product - { - #[ORM\Id] - #[ORM\GeneratedValue] - #[ORM\Column(type: 'integer')] - #[Groups(['list_product'])] - private int $id; + You can also :doc:`create your context builders ` + to have autocompletion, validation, and documentation for your custom + context values. - #[ORM\Column(type: 'string', length: 255)] - #[Groups(['list_product'])] - private string $name; +Configure Context on a Specific Property +........................................ - #[ORM\Column(type: 'text')] - private string $description; - } +At last, you can also configure context values on a specific object +property. For instance, to configure the datetime format: -In this example, the ``id`` and the ``name`` properties belong to the -``show_product`` and ``list_product`` groups. The ``description`` property -only belongs to the ``show_product`` group. +.. configuration-block:: -.. versionadded:: 6.4 + .. code-block:: php-attributes - The support of the ``#[Groups]`` attribute on class level was - introduced in Symfony 6.4. + // src/Model/Person.php -Now that your groups are defined, you can choose which groups to use when -serializing:: + // ... + use Symfony\Component\Serializer\Attribute\Context; + use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; - use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder; + class Person + { + #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])] + public \DateTimeImmutable $createdAt; - $context = (new ObjectNormalizerContextBuilder()) - ->withGroups('show_product') - ->toArray(); + // ... + } - $json = $serializer->serialize($product, 'json', $context); + .. code-block:: yaml -.. tip:: + # config/serializer/person.yaml + App\Model\Person: + attributes: + createdAt: + contexts: + - context: { datetime_format: 'Y-m-d' } - The value of the ``groups`` key can be a single string, or an array of strings. + .. code-block:: xml -In addition to the ``#[Groups]`` attribute, the Serializer component also -supports YAML or XML files. These files are automatically loaded when being -stored in one of the following locations: + + + + + + + Y-m-d + + + + -* All ``*.yaml`` and ``*.xml`` files in the ``config/serializer/`` - directory. -* The ``serialization.yaml`` or ``serialization.xml`` file in - the ``Resources/config/`` directory of a bundle; -* All ``*.yaml`` and ``*.xml`` files in the ``Resources/config/serialization/`` - directory of a bundle. +.. note:: -.. _serializer-enabling-metadata-cache: + When using YAML or XML, the mapping files must be placed in one of + these locations: -Using Nested Attributes ------------------------ + * All ``*.yaml`` and ``*.xml`` files in the ``config/serializer/`` + directory. + * The ``serialization.yaml`` or ``serialization.xml`` file in the + ``Resources/config/`` directory of a bundle; + * All ``*.yaml`` and ``*.xml`` files in the ``Resources/config/serialization/`` + directory of a bundle. -To map nested properties, use the ``SerializedPath`` configuration to define -their paths using a :doc:`valid PropertyAccess syntax `: +You can also specify a context specific to normalization or denormalization: .. configuration-block:: .. code-block:: php-attributes - namespace App\Model; + // src/Model/Person.php - use Symfony\Component\Serializer\Attribute\SerializedPath; + // ... + use Symfony\Component\Serializer\Attribute\Context; + use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; class Person { - #[SerializedPath('[profile][information][birthday]')] - private string $birthday; + #[Context( + normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'], + denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339], + )] + public \DateTimeImmutable $createdAt; // ... } .. code-block:: yaml + # config/serializer/person.yaml App\Model\Person: attributes: - dob: - serialized_path: '[profile][information][birthday]' + createdAt: + contexts: + - normalizationContext: { datetime_format: 'Y-m-d' } + denormalizationContext: { datetime_format: !php/const \DateTime::RFC3339 } .. code-block:: xml + - + + + Y-m-d + + + + Y-m-d\TH:i:sP + + -.. versionadded:: 6.2 - - The option to configure a ``SerializedPath`` was introduced in Symfony 6.2. - -Using the configuration from above, denormalizing with a metadata-aware -normalizer will write the ``birthday`` field from ``$data`` onto the ``Person`` -object:: +.. _serializer-context-group: - $data = [ - 'profile' => [ - 'information' => [ - 'birthday' => '01-01-1970', - ], - ], - ]; - $person = $normalizer->denormalize($data, Person::class, 'any'); - $person->getBirthday(); // 01-01-1970 +You can also restrict the usage of a context to some +:ref:`groups `: -When using attributes, the ``SerializedPath`` can either -be set on the property or the associated _getter_ method. The ``SerializedPath`` -cannot be used in combination with a ``SerializedName`` for the same property. - -Configuring the Metadata Cache ------------------------------- +.. configuration-block:: -The metadata for the serializer is automatically cached to enhance application -performance. By default, the serializer uses the ``cache.system`` cache pool -which is configured using the :ref:`cache.system ` -option. + .. code-block:: php-attributes -Enabling a Name Converter -------------------------- + // src/Model/Person.php -The use of a :ref:`name converter ` -service can be defined in the configuration using the :ref:`name_converter ` -option. + // ... + use Symfony\Component\Serializer\Attribute\Context; + use Symfony\Component\Serializer\Attribute\Groups; + use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; -The built-in :ref:`CamelCase to snake_case name converter ` -can be enabled by using the ``serializer.name_converter.camel_case_to_snake_case`` -value: + class Person + { + #[Groups(['extended'])] + #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])] + #[Context( + context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED], + groups: ['extended'], + )] + public \DateTimeImmutable $createdAt; -.. configuration-block:: + // ... + } .. code-block:: yaml - # config/packages/framework.yaml - framework: - # ... - serializer: - name_converter: 'serializer.name_converter.camel_case_to_snake_case' + # config/serializer/person.yaml + App\Model\Person: + attributes: + createdAt: + groups: [extended] + contexts: + - context: { datetime_format: !php/const \DateTime::RFC3339 } + - context: { datetime_format: !php/const \DateTime::RFC3339_EXTENDED } + groups: [extended] .. code-block:: xml - - - - - + + + + + + extended - .. code-block:: php + + Y-m-d\TH:i:sP + + + Y-m-d\TH:i:s.vP + extended + + + + - // config/packages/framework.php - use Symfony\Config\FrameworkConfig; +The attribute can be repeated as much as needed on a single property. +Context without group is always applied first. Then context for the +matching groups are merged in the provided order. + +If you repeat the same context in multiple properties, consider using the +``#[Context]`` attribute on your class to apply that context configuration to +all the properties of the class:: + + namespace App\Model; + + use Symfony\Component\Serializer\Attribute\Context; + use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; + + #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])] + #[Context( + context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED], + groups: ['extended'], + )] + class Person + { + // ... + } + +Serializing to or from PHP Arrays +--------------------------------- + +The default :class:`Symfony\\Component\\Serializer\\Serializer` can also be +used to only perform one step of the :ref:`two step serialization process ` +by using the respective interface: + +.. configuration-block:: + + .. code-block:: php-symfony + + use Symfony\Component\Serializer\Encoder\DecoderInterface; + use Symfony\Component\Serializer\Encoder\EncoderInterface; + use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + // ... + + class PersonController extends AbstractController + { + public function index(DenormalizerInterface&NormalizerInterface $serializer): Response + { + $person = new Person('Jane Doe', 39, false); + + // use normalize() to convert a PHP object to an array + $personArray = $serializer->normalize($person, 'json'); + + // ...and denormalize() to convert an array back to a PHP object + $personCopy = $serializer->denormalize($personArray, Person::class); + + // ... + } + + public function json(DecoderInterface&EncoderInterface $serializer): Response + { + $data = ['name' => 'Jane Doe']; + + // use encode() to transform PHP arrays into another format + $json = $serializer->encode($data, 'json'); + + // ...and decode() to transform any format to just PHP arrays (instead of objects) + $data = $serializer->decode('{"name":"Charlie Doe"}', 'json'); + // $data contains ['name' => 'Charlie Doe'] + } + } + + .. code-block:: php-standalone + + use App\Model\Person; + use Symfony\Component\Serializer\Encoder\JsonEncoder; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; + + $encoders = [new JsonEncoder()]; + $normalizers = [new ObjectNormalizer()]; + $serializer = new Serializer($normalizers, $encoders); + + // use normalize() to convert a PHP object to an array + $personArray = $serializer->normalize($person, 'json'); + + // ...and denormalize() to convert an array back to a PHP object + $personCopy = $serializer->denormalize($personArray, Person::class); + + $data = ['name' => 'Jane Doe']; + + // use encode() to transform PHP arrays into another format + $json = $serializer->encode($data, 'json'); + + // ...and decode() to transform any format to just PHP arrays (instead of objects) + $data = $serializer->decode('{"name":"Charlie Doe"}', 'json'); + // $data contains ['name' => 'Charlie Doe'] + +.. _serializer_ignoring-attributes: + +Ignoring Properties +------------------- + +The ``ObjectNormalizer`` normalizes *all* properties of an object and all +methods starting with ``get*()``, ``has*()``, ``is*()`` and ``can*()``. +Some properties or methods should never be serialized. You can exclude +them using the ``#[Ignore]`` attribute: + +.. configuration-block:: + + .. code-block:: php-attributes + + // src/Model/Person.php + namespace App\Model; + + use Symfony\Component\Serializer\Attribute\Ignore; + + class Person + { + // ... + + #[Ignore] + public function isPotentiallySpamUser(): bool + { + // ... + } + } + + .. code-block:: yaml + + App\Model\Person: + attributes: + potentiallySpamUser: + ignore: true + + .. code-block:: xml + + + + + + + + +The ``potentiallySpamUser`` property will now never be serialized: + +.. configuration-block:: + + .. code-block:: php-symfony + + use App\Model\Person; + + // ... + $person = new Person('Jane Doe', 32, false); + $json = $serializer->serialize($person, 'json'); + // $json contains {"name":"Jane Doe","age":32,"sportsperson":false} + + $person1 = $serializer->deserialize( + '{"name":"Jane Doe","age":32,"sportsperson":false","potentiallySpamUser":false}', + Person::class, + 'json' + ); + // the "potentiallySpamUser" value is ignored + + .. code-block:: php-standalone + + use App\Model\Person; + use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; + use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; + + // ... + + // you need to pass a class metadata factory with a loader to the + // ObjectNormalizer when reading mapping information like Ignore or Groups. + // E.g. when using PHP attributes: + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $normalizers = [new ObjectNormalizer($classMetadataFactory)]; + + $serializer = new Serializer($normalizers, $encoders); + + $person = new Person('Jane Doe', 32, false); + $json = $serializer->serialize($person, 'json'); + // $json contains {"name":"Jane Doe","age":32,"sportsperson":false} + + $person1 = $serializer->deserialize( + '{"name":"Jane Doe","age":32,"sportsperson":false","potentiallySpamUser":false}', + Person::class, + 'json' + ); + // the "potentiallySpamUser" value is ignored + +Ignoring Attributes Using the Context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also pass an array of attribute names to ignore at runtime using +the ``ignored_attributes`` context options:: + + use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + + // ... + $person = new Person('Jane Doe', 32, false); + $json = $serializer->serialize($person, 'json', + [ + AbstractNormalizer::IGNORED_ATTRIBUTES => ['age'], + ]); + // $json contains {"name":"Jane Doe","sportsperson":false} + +However, this can quickly become unmaintainable if used excessively. See +the next section about *serialization groups* for a better solution. + +.. _serializer-groups-attribute: + +Selecting Specific Properties +----------------------------- + +Instead of excluding a property or method in all situations, you might need +to exclude some properties in one place, but serialize them in another. +Groups are a handy way to achieve this. + +You can add the ``#[Groups]`` attribute to your class: + +.. configuration-block:: + + .. code-block:: php-attributes + + // src/Model/Person.php + namespace App\Model; + + use Symfony\Component\Serializer\Attribute\Groups; + + class Person + { + #[Groups(["admin-view"])] + private int $age; + + #[Groups(["public-view"])] + private string $name; + + #[Groups(["public-view"])] + private bool $sportsperson; + + // ... + } + + .. code-block:: yaml + + # config/serializer/person.yaml + App\Model\Person: + attributes: + age: + groups: ['admin-view'] + name: + groups: ['public-view'] + sportsperson: + groups: ['public-view'] + + .. code-block:: xml + + + + + + + admin-view + + + public-view + + + public-view + + + + +You can now choose which groups to use when serializing:: + + $json = $serializer->serialize( + $person, + 'json', + ['groups' => 'public-view'] + ); + // $json contains {"name":"Jane Doe","sportsperson":false} + + // you can also pass an array of groups + $json = $serializer->serialize( + $person, + 'json', + ['groups' => ['public-view', 'admin-view']] + ); + // $json contains {"name":"Jane Doe","age":32,"sportsperson":false} + + // or use the special "*" value to select all groups + $json = $serializer->serialize( + $person, + 'json', + ['groups' => '*'] + ); + // $json contains {"name":"Jane Doe","age":32,"sportsperson":false} + +Using the Serialization Context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +At last, you can also use the ``attributes`` context option to select +properties at runtime:: + + use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + // ... + + $json = $serializer->serialize($person, 'json', [ + AbstractNormalizer::ATTRIBUTES => ['name', 'company' => ['name']] + ]); + // $json contains {"name":"Dunglas","company":{"name":"Les-Tilleuls.coop"}} + +Only attributes that are :ref:`not ignored ` +are available. If serialization groups are set, only attributes allowed by +those groups can be used. + +.. _serializer-handling-arrays: + +Handling Arrays +--------------- + +The serializer is capable of handling arrays of objects. Serializing arrays +works just like serializing a single object:: + + use App\Model\Person; + + // ... + $person1 = new Person('Jane Doe', 39, false); + $person2 = new Person('John Smith', 52, true); + + $persons = [$person1, $person2]; + $JsonContent = $serializer->serialize($persons, 'json'); + + // $jsonContent contains [{"name":"Jane Doe","age":39,"sportsman":false},{"name":"John Smith","age":52,"sportsman":true}] + +To deserialize a list of objects, you have to append ``[]`` to the type +parameter:: + + // ... + + $jsonData = ...; // the serialized JSON data from the previous example + $persons = $serializer->deserialize($JsonData, Person::class.'[]', 'json'); + +For nested classes, you have to add a PHPDoc type to the property/setter:: + + // src/Model/UserGroup.php + namespace App\Model; + + class UserGroup + { + private array $members; + + // ... + + /** + * @param Person[] $members + */ + public function setMembers(array $members): void + { + $this->members = $members; + } + } + +.. tip:: + + The Serializer also supports array types used in static analysis, like + ``list`` and ``array``. Make sure the + ``phpstan/phpdoc-parser`` and ``phpdocumentor/reflection-docblock`` + packages are installed (these are part of the ``symfony/serializer-pack``). + +.. _serializer-nested-structures: + +Deserializing Nested Structures +------------------------------- + +.. versionadded:: 6.2 + + The option to configure a ``SerializedPath`` was introduced in Symfony 6.2. + +Some APIs might provide verbose nested structures that you want to flatten +in the PHP object. For instance, imagine a JSON response like this: + +.. code-block:: json + + { + "id": "123", + "profile": { + "username": "jdoe", + "personal_information": { + "full_name": "Jane Doe" + } + } + } + +You may wish to serialize this information to a single PHP object like:: + + class Person + { + private int $id; + private string $username; + private string $fullName; + } + +Use the ``#[SerializedPath]`` to specify the path of the nested property +using :doc:`valid PropertyAccess syntax `: + +.. configuration-block:: + + .. code-block:: php-attributes + + namespace App\Model; + + use Symfony\Component\Serializer\Attribute\SerializedPath; + + class Person + { + private int $id; + + #[SerializedPath('[profile][username]')] + private string $username; + + #[SerializedPath('[profile][personal_information][full_name]')] + private string $fullName; + } + + .. code-block:: yaml + + App\Model\Person: + attributes: + username: + serialized_path: '[profile][username]' + fullName: + serialized_path: '[profile][personal_information][full_name]' + + .. code-block:: xml + + + + + + + + + +.. caution:: + + The ``SerializedPath`` cannot be used in combination with a + ``SerializedName`` for the same property. + +The ``#[SerializedPath]`` attribute also applies to the serialization of a +PHP object:: + + use App\Model\Person; + // ... + + $person = new Person(123, 'jdoe', 'Jane Doe'); + $jsonContent = $serializer->serialize($person, 'json'); + // $jsonContent contains {"id":123,"profile":{"username":"jdoe","personal_information":{"full_name":"Jane Doe"}}} + +.. _serializer-name-conversion: + +Converting Property Names when Serializing and Deserializing +------------------------------------------------------------ + +Sometimes serialized attributes must be named differently than properties +or getter/setter methods of PHP classes. This can be achieved using name +converters. + +The serializer service uses the +:class:`Symfony\\Component\\Serializer\\NameConverter\\MetadataAwareNameConverter`. +With this name converter, you can change the name of an attribute using +the ``#[SerializedName]`` attribute: + +.. configuration-block:: + + .. code-block:: php-attributes + + // src/Model/Person.php + namespace App\Model; + + use Symfony\Component\Serializer\Attribute\SerializedName; + + class Person + { + #[SerializedName('customer_name')] + private string $name; + + // ... + } + + .. code-block:: yaml + + # config/serializer/person.yaml + App\Entity\Person: + attributes: + name: + serialized_name: customer_name + + .. code-block:: xml + + + + + + + + + +This custom mapping is used to convert property names when serializing and +deserializing objects: + +.. configuration-block:: + + .. code-block:: php-symfony + + // ... + + $json = $serializer->serialize($person, 'json'); + // $json contains {"customer_name":"Jane Doe", ...} + + .. code-block:: php-standalone + + use App\Model\Person; + use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; + use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; + use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; + + // ... + + // Configure a loader to retrieve mapping information like SerializedName. + // E.g. when using PHP attributes: + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $normalizers = [ + new ObjectNormalizer($classMetadataFactory, $nameConverter), + ]; + + $serializer = new Serializer($normalizers, $encoders); + + $person = new Person('Jane Doe', 32, false); + $json = $serializer->serialize($person, 'json'); + // $json contains {"customer_name":"Jane Doe", ...} + +.. seealso:: + + You can also create a custom name converter class. Read more about this + in :doc:`/serializer/custom_name_converter`. + +.. _using-camelized-method-names-for-underscored-attributes: + +CamelCase to snake_case +~~~~~~~~~~~~~~~~~~~~~~~ + +In many formats, it's common to use underscores to separate words (also known +as snake_case). However, in Symfony applications is common to use camelCase to +name properties. + +Symfony provides a built-in name converter designed to transform between +snake_case and CamelCased styles during serialization and deserialization +processes. You can use it instead of the metadata aware name converter by +setting the ``name_converter`` setting to +``serializer.name_converter.camel_case_to_snake_case``: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/serializer.yaml + framework: + serializer: + name_converter: 'serializer.name_converter.camel_case_to_snake_case' + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/packages/serializer.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $framework->serializer() + ->nameConverter('serializer.name_converter.camel_case_to_snake_case') + ; + }; + + .. code-block:: php-standalone + + use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + + // ... + $normalizers = [ + new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter()), + ]; + $serializer = new Serializer($normalizers, $encoders); + +.. _serializer-built-in-normalizers: + +Serializer Normalizers +---------------------- + +By default, the serializer service is configured with the following +normalizers (in order of priority): + +:class:`Symfony\\Component\\Serializer\\Normalizer\\UnwrappingDenormalizer` + Can be used to only denormalize a part of the input, read more about + this :ref:`later in this article `. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` + Normalizes :class:`Symfony\\Component\\ErrorHandler\\Exception\\FlattenException` + errors according to the API Problem spec `RFC 7807`_. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\UidNormalizer` + Normalizes objects that extend :class:`Symfony\\Component\\Uid\\AbstractUid`. + + The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Uuid` + is the `RFC 4122`_ format (example: ``d9e7a184-5d5b-11ea-a62a-3499710062d0``). + The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Ulid` + is the Base 32 format (example: ``01E439TP9XJZ9RPFH3T1PYBCR8``). + You can change the string format by setting the serializer context option + ``UidNormalizer::NORMALIZATION_FORMAT_KEY`` to ``UidNormalizer::NORMALIZATION_FORMAT_BASE_58``, + ``UidNormalizer::NORMALIZATION_FORMAT_BASE_32`` or ``UidNormalizer::NORMALIZATION_FORMAT_RFC_4122``. + + Also it can denormalize ``uuid`` or ``ulid`` strings to :class:`Symfony\\Component\\Uid\\Uuid` + or :class:`Symfony\\Component\\Uid\\Ulid`. The format does not matter. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` + This normalizes between :phpclass:`DateTimeInterface` objects (e.g. + :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) and strings. + + By default, the `RFC 3339`_ format is used when normalizing the value. + Use ``DateTimeNormalizer::FORMAT_KEY`` and ``DateTimeNormalizer::TIMEZONE_KEY`` + to change the format. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer` + This normalizer converts objects that implement + :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface` + into a list of errors according to the `RFC 7807`_ standard. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer` + This normalizer converts between :phpclass:`DateTimeZone` objects and strings that + represent the name of the timezone according to the `list of PHP timezones`_. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer` + This normalizes between :phpclass:`DateInterval` objects and strings. + By default, the ``P%yY%mM%dDT%hH%iM%sS`` format is used. Use the + ``DateIntervalNormalizer::FORMAT_KEY`` option to change this. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` + This normalizer works with classes that implement + :class:`Symfony\\Component\\Form\\FormInterface`. + + It will get errors from the form and normalize them according to the + API Problem spec `RFC 7807`_. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer` + This normalizer converts objects implementing :class:`Symfony\\Contracts\\Translation\\TranslatableInterface` + to a translated string using the :doc:`translator `. + + You can define the locale to use to translate the object by setting the + ``TranslatableNormalizer::NORMALIZATION_LOCALE_KEY`` context option. + + .. versionadded:: 6.4 + + The ``UidNormalizer`` normalization formats were introduced in Symfony 5.3. + The :class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer` + was introduced in Symfony 6.4. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer` + This normalizer converts between :phpclass:`BackedEnum` enums and + strings or integers. + + By default, an exception is thrown when data is not a valid backed enumeration. If you + want ``null`` instead, you can set the ``BackedEnumNormalizer::ALLOW_INVALID_VALUES`` option. + + .. versionadded:: 6.3 + + The ``BackedEnumNormalizer::ALLOW_INVALID_VALUES`` context option was introduced in Symfony 6.3. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` + This normalizer converts between :phpclass:`SplFileInfo` objects and a + `data URI`_ string (``data:...``) such that files can be embedded into + serialized data. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer` + This normalizer works with classes that implement :phpclass:`JsonSerializable`. + + It will call the :phpmethod:`JsonSerializable::jsonSerialize` method and + then further normalize the result. This means that nested + :phpclass:`JsonSerializable` classes will also be normalized. + + This normalizer is particularly helpful when you want to gradually migrate + from an existing codebase using simple :phpfunction:`json_encode` to the Symfony + Serializer by allowing you to mix which normalizers are used for which classes. + + Unlike with :phpfunction:`json_encode` circular references can be handled. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer` + This denormalizer converts an array of arrays to an array of objects + (with the given type). See :ref:`Handling Arrays `. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` + This is the most powerful default normalizer and used for any object + that could not be normalized by the other normalizers. + + It leverages the :doc:`PropertyAccess Component ` + to read and write in the object. This allows it to access properties + directly or using getters, setters, hassers, issers, canners, adders and + removers. Names are generated by removing the ``get``, ``set``, + ``has``, ``is``, ``add`` or ``remove`` prefix from the method name and + transforming the first letter to lowercase (e.g. ``getFirstName()`` -> + ``firstName``). + + During denormalization, it supports using the constructor as well as + the discovered methods. + +:ref:`serializer.encoder ` + +.. danger:: + + Always make sure the ``DateTimeNormalizer`` is registered when + serializing the ``DateTime`` or ``DateTimeImmutable`` classes to avoid + excessive memory usage and exposing internal details. + +Built-in Normalizers +~~~~~~~~~~~~~~~~~~~~ + +Besides the normalizers registered by default (see previous section), the +serializer component also provides some extra normalizers.You can register +these by defining a service and tag it with :ref:`serializer.normalizer `. +For instance, to use the ``CustomNormalizer`` you have to define a service +like: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + # ... + + # if you're using autoconfigure, the tag will be automatically applied + Symfony\Component\Serializer\Normalizer\CustomNormalizer: + tags: + # register the normalizer with a high priority (called earlier) + - { name: 'serializer.normalizer', priority: 500 } + + .. code-block:: xml + + + + + + + + + + + + + + + + + .. code-block:: php - return static function (FrameworkConfig $framework): void { - $framework->serializer()->nameConverter('serializer.name_converter.camel_case_to_snake_case'); + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use Symfony\Component\Serializer\Normalizer\CustomNormalizer; + + return function(ContainerConfigurator $container) { + // ... + + // if you're using autoconfigure, the tag will be automatically applied + $services->set(CustomNormalizer::class) + // register the normalizer with a high priority (called earlier) + ->tag('serializer.normalizer', [ + 'priority' => 500, + ]) + ; }; +:class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer` + This normalizer calls a method on the PHP object when normalizing. The + PHP object must implement :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface` + and/or :class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizableInterface`. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` + This normalizer is an alternative to the default ``ObjectNormalizer``. + It reads the content of the class by calling the "getters" (public + methods starting with ``get``, ``has``, ``is`` or ``can``). It will + denormalize data by calling the constructor and the "setters" (public + methods starting with ``set``). + + Objects are normalized to a map of property names and values (names are + generated by removing the ``get`` prefix from the method name and transforming + the first letter to lowercase; e.g. ``getFirstName()`` -> ``firstName``). + +:class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer` + This is yet another alternative to the ``ObjectNormalizer``. This + normalizer directly reads and writes public properties as well as + **private and protected** properties (from both the class and all of + its parent classes) by using `PHP reflection`_. It supports calling the + constructor during the denormalization process. + + Objects are normalized to a map of property names to property values. + + You can also limit the normalizer to only use properties with a specific + visibility (e.g. only public properties) using the + ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option. You can set it + to any combination of the ``PropertyNormalizer::NORMALIZE_PUBLIC``, + ``PropertyNormalizer::NORMALIZE_PROTECTED`` and + ``PropertyNormalizer::NORMALIZE_PRIVATE`` constants:: + + use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; + // ... + + $json = $serializer->serialize($person, 'json', [ + // only serialize public properties + PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC, + + // serialize public and protected properties + PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED, + ]); + + .. versionadded:: 6.2 + + The ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option and its + values were introduced in Symfony 6.2. + Debugging the Serializer ------------------------ +.. versionadded:: 6.3 + + The debug:serializer`` command was introduced in Symfony 6.3. + Use the ``debug:serializer`` command to dump the serializer metadata of a given class: @@ -553,39 +1520,609 @@ given class: | | ] | +----------+------------------------------------------------------------+ +Advanced Serialization +---------------------- + +Skipping ``null`` Values +~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, the Serializer will preserve properties containing a ``null`` value. +You can change this behavior by setting the ``AbstractObjectNormalizer::SKIP_NULL_VALUES`` context option +to ``true``:: + + class Person + { + public string $name = 'Jane Doe'; + public ?string $gender = null; + } + + $jsonContent = $serializer->serialize(new Person(), 'json', [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + ]); + // $jsonContent contains {"name":"Jane Doe"} + +Handling Uninitialized Properties +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In PHP, typed properties have an ``uninitialized`` state which is different +from the default ``null`` of untyped properties. When you try to access a typed +property before giving it an explicit value, you get an error. + +To avoid the serializer throwing an error when serializing or normalizing +an object with uninitialized properties, by default the ``ObjectNormalizer`` +catches these errors and ignores such properties. + +You can disable this behavior by setting the +``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context option to +``false``:: + + class Person { + public string $name = 'Jane Doe'; + public string $phoneNumber; // uninitialized + } + + $jsonContent = $normalizer->serialize(new Dummy(), 'json', [ + AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false, + ]); + // throws Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException + // as the ObjectNormalizer cannot read uninitialized properties + +.. note:: + + Using :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer` + or :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` + with ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context + option set to ``false`` will throw an ``\Error`` instance if the given + object has uninitialized properties as the normalizers cannot read them + (directly or via getter/isser methods). + +.. _component-serializer-handling-circular-references: + +Handling Circular References +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Circular references are common when dealing with associated objects:: + + class Organization + { + public function __construct( + private string $name, + private array $members = [] + ) { + } + + public function getName(): string + { + return $this->name; + } + + public function addMember(Member $member): void + { + $this->members[] = $member; + } + + public function getMembers(): array + { + return $this->members; + } + } + + class Member + { + private Organization $organization; + + public function __construct( + private string $name + ) { + } + + public function getName(): string + { + return $this->name; + } + + public function setOrganization(Organization $organization): void + { + $this->organization = $organization; + } + + public function getOrganization(): Organization + { + return $this->organization; + } + } + +To avoid infinite loops, the normalizers throw a +:class:`Symfony\\Component\\Serializer\\Exception\\CircularReferenceException` +when such a case is encountered:: + + $organization = new Organization('Les-Tilleuls.coop'); + $member = new Member('Kévin'); + + $organization->addMember($member); + $member->setOrganization($organization); + + $jsonContent = $serializer->serialize($organization, 'json'); + // throws a CircularReferenceException + +The key ``circular_reference_limit`` in the context sets the number of +times it will serialize the same object before considering it a circular +reference. The default value is ``1``. + +Instead of throwing an exception, circular references can also be handled +by custom callables. This is especially useful when serializing entities +having unique identifiers:: + + use Symfony\Component\Serializer\Exception\CircularReferenceException; + + $context = [ + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (object $object, ?string $format, array $context): string { + if (!$object instanceof Organization) { + throw new CircularReferenceException('A circular reference has been detected when serializing the object of class "'.get_debug_type($object).'".'); + } + + // serialize the nested Organization with only the name (and not the members) + return $object->getName(); + }, + ]; + + $jsonContent = $serializer->serialize($organization, 'json', $context); + // $jsonContent contains {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]} + +.. _serializer_handling-serialization-depth: + +Handling Serialization Depth +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The serializer can also detect nested objects of the same class and limit +the serialization depth. This is useful for tree structures, where the same +object is nested multiple times. + +For instance, assume a data structure of a family tree:: + + // ... + class Person + { + // ... + + public function __construct( + private string $name, + private ?self $mother + ) { + } + + public function getName(): string + { + return $this->name; + } + + public function getMother(): ?self + { + return $this->mother; + } + + // ... + } + + // ... + $greatGrandmother = new Person('Elizabeth', null); + $grandmother = new Person('Jane', $greatGrandmother); + $mother = new Person('Sophie', $grandmother); + $child = new Person('Joe', $mother); + +You can specify the maximum depth for a given property. For instance, you +can set the max depth to ``1`` to always only serialize someone's mother +(and not their grandmother, etc.): + +.. configuration-block:: + + .. code-block:: php-attributes + + // src/Model/Person.php + namespace App\Model; + + use Symfony\Component\Serializer\Attribute\MaxDepth; + + class Person + { + #[MaxDepth(1)] + private ?self $mother; + + // ... + } + + .. code-block:: yaml + + # config/serializer/person.yaml + App\Model\Person: + attributes: + mother: + max_depth: 1 + + .. code-block:: xml + + + + + + + + + +To limit the serialization depth, you must set the +``AbstractObjectNormalizer::ENABLE_MAX_DEPTH`` key to ``true`` in the +context (or the default context specified in ``framework.yaml``):: + + // ... + $greatGrandmother = new Person('Elizabeth', null); + $grandmother = new Person('Jane', $greatGrandmother); + $mother = new Person('Sophie', $grandmother); + $child = new Person('Joe', $mother); + + $jsonContent = $serializer->serialize($child, null, [ + AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true + ]); + // $jsonContent contains {"name":"Joe","mother":{"name":"Sophie"}} + +You can also configure a custom callable that is used when the maximum +depth is reached. This can be used to for instance return the unique +identifier of the next nested object, instead of omitting the property:: + + use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; + // ... + + $greatGrandmother = new Person('Elizabeth', null); + $grandmother = new Person('Jane', $greatGrandmother); + $mother = new Person('Sophie', $grandmother); + $child = new Person('Joe', $mother); + + // all callback parameters are optional (you can omit the ones you don't use) + $maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, ?string $format = null, array $context = []): string { + // return only the name of the next person in the tree + return $innerObject instanceof Person ? $innerObject->getName() : null; + }; + + $jsonContent = $serializer->serialize($child, null, [ + AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true, + AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler, + ]); + // $jsonContent contains {"name":"Joe","mother":{"name":"Sophie","mother":"Jane"}} + +Using Callbacks to Serialize Properties with Object Instances +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When serializing, you can set a callback to format a specific object +property. This can be used instead of +:ref:`defining the context for a group `:: + + $person = new Person('cordoval', 34); + $person->setCreatedAt(new \DateTime('now')); + + $context = [ + AbstractNormalizer::CALLBACKS => [ + // all callback parameters are optional (you can omit the ones you don't use) + 'createdAt' => function (object $attributeValue, object $object, string $attributeName, ?string $format = null, array $context = []) { + return $attributeValue instanceof \DateTime ? $attributeValue->format(\DateTime::ATOM) : ''; + }, + ], + ]; + $jsonContent = $serializer->serialize($person, 'json'); + // $jsonContent contains {"name":"cordoval","age":34,"createdAt":"2014-03-22T09:43:12-0500"} + +Advanced Deserialization +------------------------ + +Require all Properties +~~~~~~~~~~~~~~~~~~~~~~ + .. versionadded:: 6.3 - The debug:serializer`` command was introduced in Symfony 6.3. + The ``AbstractNormalizer::PREVENT_NULLABLE_FALLBACK`` context option + was introduced in Symfony 6.3. -Going Further with the Serializer ---------------------------------- +By default, the Serializer will add ``null`` to nullable properties when +the parameters for those are not provided. You can change this behavior by +setting the ``AbstractNormalizer::REQUIRE_ALL_PROPERTIES`` context option +to ``true``:: + + class Person + { + public function __construct( + public string $firstName, + public ?string $lastName, + ) { + } + } + + // ... + $data = ['firstName' => 'John']; + $person = $serializer->deserialize($data, Person::class, 'json', [ + AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true, + ]); + // throws Symfony\Component\Serializer\Exception\MissingConstructorArgumentException + +Collecting Type Errors While Denormalizing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When denormalizing a payload to an object with typed properties, you'll get an +exception if the payload contains properties that don't have the same type as +the object. + +Use the ``COLLECT_DENORMALIZATION_ERRORS`` option to collect all exceptions +at once, and to get the object partially denormalized:: + + try { + $person = $serializer->deserialize($jsonString, Person::class, 'json', [ + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); + } catch (PartialDenormalizationException $e) { + $violations = new ConstraintViolationList(); + + /** @var NotNormalizableValueException $exception */ + foreach ($e->getErrors() as $exception) { + $message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType()); + $parameters = []; + if ($exception->canUseMessageForUser()) { + $parameters['hint'] = $exception->getMessage(); + } + $violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null)); + } + + // ... return violation list to the user + } + +.. _serializer-populate-existing-object: + +Deserializing in an Existing Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The serializer can also be used to update an existing object. You can do +this by configuring the ``object_to_populate`` serializer context option:: + + use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + + // ... + $person = new Person('Jane Doe', 59); + + $serializer->deserialize($jsonData, Person::class, 'json', [ + AbstractNormalizer::OBJECT_TO_POPULATE => $person, + ]); + // instead of returning a new object, $person is updated instead + +.. note:: + + The ``AbstractNormalizer::OBJECT_TO_POPULATE`` option is only used for + the top level object. If that object is the root of a tree structure, + all child elements that exist in the normalized data will be re-created + with new instances. + + When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` context + option is set to ``true``, existing children of the root ``OBJECT_TO_POPULATE`` + are updated from the normalized data, instead of the denormalizer + re-creating them. This only works for single child objects, not for + arrays of objects. Those will still be replaced when present in the + normalized data. + +.. _serializer_interfaces-and-abstract-classes: + +Deserializing Interfaces and Abstract Classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When working with associated objects, a property sometimes reference an +interface or abstract class. When deserializing these properties, the +Serializer has to know which concrete class to initialize. This is done +using a *discriminator class mapping*. + +Imagine there is an ``InvoiceItemInterface`` that is implemented by the +``Product`` and ``Shipping`` objects. When serializing an object, the +serializer will add an extra "discriminator attribute". This contains +either ``product`` or ``shipping``. The discriminator class map maps +these type names to the real PHP class name when deserializing: + +.. configuration-block:: + + .. code-block:: php-attributes + + namespace App\Model; + + use Symfony\Component\Serializer\Attribute\DiscriminatorMap; + + #[DiscriminatorMap( + typeProperty: 'type', + mapping: [ + 'product' => Product::class, + 'shipping' => Shipping::class, + ] + )] + interface InvoiceItemInterface + { + // ... + } + + .. code-block:: yaml + + App\Model\InvoiceItemInterface: + discriminator_map: + type_property: type + mapping: + product: 'App\Model\Product' + shipping: 'App\Model\Shipping' + + .. code-block:: xml -`API Platform`_ provides an API system supporting the following formats: + + + + + + + + + + +With the discriminator map configured, the serializer can now pick the +correct class for properties typed as `InvoiceItemInterface`:: + +.. configuration-block:: + + .. code-block:: php-symfony + + class InvoiceLine + { + public function __construct( + private InvoiceItemInterface $invoiceItem + ) { + $this->invoiceItem = $invoiceItem; + } + + public function getInvoiceItem(): InvoiceItemInterface + { + return $this->invoiceItem; + } + + // ... + } + + // ... + $invoiceLine = new InvoiceLine(new Product()); + + $jsonString = $serializer->serialize($invoiceLine, 'json'); + // $jsonString contains {"type":"product",...} + + $invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json'); + // $invoiceLine contains new InvoiceLine(new Product(...)) + + .. code-block:: php-standalone + + // ... + use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; + use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; + use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; + + class InvoiceLine + { + public function __construct( + private InvoiceItemInterface $invoiceItem + ) { + $this->invoiceItem = $invoiceItem; + } + + public function getInvoiceItem(): InvoiceItemInterface + { + return $this->invoiceItem; + } + + // ... + } + + // ... + + // Configure a loader to retrieve mapping information like DiscriminatorMap. + // E.g. when using PHP attributes: + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); + $normalizers = [ + new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator), + ]; + + $serializer = new Serializer($normalizers, $encoders); + + $invoiceLine = new InvoiceLine(new Product()); + + $jsonString = $serializer->serialize($invoiceLine, 'json'); + // $jsonString contains {"type":"product",...} + + $invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json'); + // $invoiceLine contains new InvoiceLine(new Product(...)) + +.. _serializer-unwrapping-denormalizer: + +Deserializing Input Partially (Unwrapping) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The serializer will always deserialize the complete input string into PHP +values. When connecting with third party APIs, you often only need a +specific part of the returned response. + +To avoid deserializing the whole response, you can use the +:class:`Symfony\\Component\\Serializer\\Normalizer\\UnwrappingDenormalizer` +and "unwrap" the input data:: + + $jsonData = '{"result":"success","data":{"person":{"name": "Jane Doe","age":57}}}'; + $data = $serialiser->deserialize($jsonData, Object::class, [ + UnwrappingDenormalizer::UNWRAP_PATH => '[data][person]', + ]); + // $data is Person(name: 'Jane Doe', age: 57) + +The ``unwrap_path`` is a :ref:`property path ` +of the PropertyAccess component, applied on the denormalized array. + +Handling Constructor Arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the class constructor defines arguments, as usually happens with +`Value Objects`_, the serializer will match the parameter names with the +deserialized attributes. If some parameters are missing, a +:class:`Symfony\\Component\\Serializer\\Exception\\MissingConstructorArgumentsException` +is thrown. + +In these cases, use the ``default_constructor_arguments`` context option to +define default values for the missing parameters:: -* `JSON-LD`_ along with the `Hydra Core Vocabulary`_ -* `OpenAPI`_ v2 (formerly Swagger) and v3 -* `GraphQL`_ -* `JSON:API`_ -* `HAL`_ -* JSON -* XML -* YAML -* CSV + use App\Model\Person; + use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + // ... -It is built on top of the Symfony Framework and its Serializer -component. It provides custom normalizers and a custom encoder, custom metadata -and a caching system. + $jsonData = '{"age":39,"name":"Jane Doe"}'; + $person = $serializer->deserialize($jsonData, Person::class, 'json', [ + AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [ + Person::class => ['sportsperson' => true], + ], + ]); + // $person is Person(name: 'Jane Doe', age: 39, sportsperson: true); + +Recursive Denormalization and Type Safety +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a ``PropertyTypeExtractor`` is available, the normalizer will also +check that the data to denormalize matches the type of the property (even +for primitive types). For instance, if a ``string`` is provided, but the +type of the property is ``int``, an +:class:`Symfony\\Component\\Serializer\\Exception\\UnexpectedValueException` +will be thrown. The type enforcement of the properties can be disabled by +setting the serializer context option +``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT`` to ``true``. + +.. _serializer-enabling-metadata-cache: + +Configuring the Metadata Cache +------------------------------ + +The metadata for the serializer is automatically cached to enhance application +performance. By default, the serializer uses the ``cache.system`` cache pool +which is configured using the :ref:`cache.system ` +option. -If you want to leverage the full power of the Symfony Serializer component, -take a look at how this bundle works. +Going Further with the Serializer +--------------------------------- .. toctree:: + :glob: :maxdepth: 1 - serializer/custom_encoders - serializer/custom_normalizer - serializer/custom_context_builders + serializer/* +.. _`JMS serializer`: https://github.com/schmittjoh/serializer .. _`API Platform`: https://api-platform.com .. _`JSON-LD`: https://json-ld.org .. _`Hydra Core Vocabulary`: https://www.hydra-cg.com/ @@ -593,3 +2130,10 @@ take a look at how this bundle works. .. _`GraphQL`: https://graphql.org .. _`JSON:API`: https://jsonapi.org .. _`HAL`: https://stateless.group/hal_specification.html +.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807 +.. _`RFC 4122`: https://tools.ietf.org/html/rfc4122 +.. _`RFC 3339`: https://tools.ietf.org/html/rfc3339#section-5.8 +.. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php +.. _`data URI`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs +.. _`PHP reflection`: https://php.net/manual/en/book.reflection.php +.. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object diff --git a/serializer/custom_context_builders.rst b/serializer/custom_context_builders.rst index 31fba6c90f5..acb6a8b6ee3 100644 --- a/serializer/custom_context_builders.rst +++ b/serializer/custom_context_builders.rst @@ -5,11 +5,9 @@ How to Create your Custom Context Builder Context builders were introduced in Symfony 6.1. -The :doc:`Serializer Component ` uses Normalizers -and Encoders to transform any data to any data-structure (e.g. JSON). -That serialization process can be configured thanks to a -:ref:`serialization context `, which can be built thanks to -:ref:`context builders `. +That serialization process of the :doc:`Serializer Component ` +can be configured by the :ref:`serialization context `, +which can be built thanks to :ref:`context builders `. Each built-in normalizer/encoder has its related context builder. However, you may want to create a custom context builder for your diff --git a/serializer/custom_encoders.rst b/serializer/custom_encoders.rst deleted file mode 100644 index dca6aa12ec4..00000000000 --- a/serializer/custom_encoders.rst +++ /dev/null @@ -1,61 +0,0 @@ -How to Create your Custom Encoder -================================= - -The :doc:`Serializer Component ` uses Normalizers -to transform any data to an array. Then, by leveraging *Encoders*, that data can -be converted into any data-structure (e.g. JSON). - -The Component provides several built-in encoders that are described -:doc:`in the serializer component ` but you may want -to use another structure that's not supported. - -Creating a new encoder ----------------------- - -Imagine you want to serialize and deserialize YAML. For that you'll have to -create your own encoder that uses the -:doc:`Yaml Component `:: - - // src/Serializer/YamlEncoder.php - namespace App\Serializer; - - use Symfony\Component\Serializer\Encoder\DecoderInterface; - use Symfony\Component\Serializer\Encoder\EncoderInterface; - use Symfony\Component\Yaml\Yaml; - - class YamlEncoder implements EncoderInterface, DecoderInterface - { - public function encode($data, string $format, array $context = []): string - { - return Yaml::dump($data); - } - - public function supportsEncoding(string $format, array $context = []): bool - { - return 'yaml' === $format; - } - - public function decode(string $data, string $format, array $context = []): array - { - return Yaml::parse($data); - } - - public function supportsDecoding(string $format, array $context = []): bool - { - return 'yaml' === $format; - } - } - -Registering it in your app --------------------------- - -If you use the Symfony Framework, then you probably want to register this encoder -as a service in your app. If you're using the :ref:`default services.yaml configuration `, -that's done automatically! - -.. tip:: - - If you're not using :ref:`autoconfigure `, make sure - to register your class as a service and tag it with ``serializer.encoder``. - -Now you'll be able to serialize and deserialize YAML! diff --git a/serializer/custom_name_converter.rst b/serializer/custom_name_converter.rst new file mode 100644 index 00000000000..82247134217 --- /dev/null +++ b/serializer/custom_name_converter.rst @@ -0,0 +1,105 @@ +How to Create your Custom Name Converter +======================================== + +The Serializer Component uses :ref:`name converters ` +to transform the attribute names (e.g. from snake_case in JSON to CamelCase +for PHP properties). + +Imagine you have the following object:: + + namespace App\Model; + + class Company + { + public string $name; + public string $address; + } + +And in the serialized form, all attributes must be prefixed by ``org_`` like +the following: + +.. code-block:: json + + {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} + +A custom name converter can handle such cases:: + + namespace App\Serializer; + + use Symfony\Component\Serializer\NameConverter\NameConverterInterface; + + class OrgPrefixNameConverter implements NameConverterInterface + { + public function normalize(string $propertyName): string + { + // during normalization, add the prefix + return 'org_'.$propertyName; + } + + public function denormalize(string $propertyName): string + { + // remove the 'org_' prefix on denormalizing + return str_starts_with($propertyName, 'org_') ? substr($propertyName, 4) : $propertyName; + } + } + +.. note:: + + You can also implement + :class:`Symfony\\Component\\Serializer\\NameConverter\\AdvancedNameConverterInterface` + to access the current class name, format and context. + +Then, configure the serializer to use your name converter: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/serializer.yaml + framework: + serializer: + # pass the service ID of your name converter + name_converter: 'App\Serializer\OrgPrefixNameConverter' + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // config/packages/serializer.php + use App\Serializer\OrgPrefixNameConverter; + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->serializer() + // pass the service ID of your name converter + ->nameConverter(OrgPrefixNameConverter::class) + ; + }; + +Now, when using the serializer in the application, all attributes will be +prefixed by ``org_``:: + + // ... + $company = new Company('Acme Inc.', '123 Main Street, Big City'); + + $json = $serializer->serialize($company, 'json'); + // {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} + $companyCopy = $serializer->deserialize($json, Company::class, 'json'); + // Same data as $company diff --git a/serializer/custom_normalizer.rst b/serializer/custom_normalizer.rst index 3d2e7cd2a7e..276c618b8ac 100644 --- a/serializer/custom_normalizer.rst +++ b/serializer/custom_normalizer.rst @@ -1,10 +1,11 @@ How to Create your Custom Normalizer ==================================== -The :doc:`Serializer component ` uses -normalizers to transform any data into an array. The component provides several -:ref:`built-in normalizers ` but you may need to create -your own normalizer to transform an unsupported data structure. +The :doc:`Serializer component ` uses normalizers to transform +any data into an array. The component provides several +ref:`built-in normalizers ` but you may +need to create your own normalizer to transform an unsupported data +structure. Creating a New Normalizer ------------------------- @@ -67,6 +68,63 @@ a service and :doc:`tagged ` with ``serializer.normaliz If you're using the :ref:`default services.yaml configuration `, this is done automatically! +If you're not using ``autoconfigure``, you have to tag the service with +``serializer.normalizer``. You can also use this method to set a priority +(higher means it's called earlier in the process): + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + # ... + + App\Serializer\TopicNormalizer: + tags: + # register the normalizer with a high priority (called earlier) + - { name: 'serializer.normalizer', priority: 500 } + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\Serializer\TopicNormalizer; + + return function(ContainerConfigurator $container) { + // ... + + // if you're using autoconfigure, the tag will be automatically applied + $services->set(TopicNormalizer::class) + // register the normalizer with a high priority (called earlier) + ->tag('serializer.normalizer', [ + 'priority' => 500, + ]) + ; + }; + Performance ----------- @@ -90,7 +148,7 @@ is called. .. note:: - All built-in :ref:`normalizers and denormalizers ` + All built-in :ref:`normalizers and denormalizers ` as well the ones included in `API Platform`_ natively implement this interface. .. deprecated:: 6.3 diff --git a/serializer/encoders.rst b/serializer/encoders.rst new file mode 100644 index 00000000000..c8bddc604ba --- /dev/null +++ b/serializer/encoders.rst @@ -0,0 +1,371 @@ +Serializer Encoders +=================== + +The Serializer component provides several built-in encoders: + +:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder` + This class encodes and decodes data in `JSON`_. + +:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder` + This class encodes and decodes data in `XML`_. + +:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder` + This encoder encodes and decodes data in `YAML`_. This encoder requires the + :doc:`Yaml Component `. + +:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder` + This encoder encodes and decodes data in `CSV`_. + +.. note:: + + You can also create your own encoder to use another structure. Read more at + :ref:`Creating a Custom Encoder ` below. + +All these encoders are enabled by default when using the Serializer component +in a Symfony application. + +The ``JsonEncoder`` +------------------- + +The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP +:phpfunction:`json_encode` and :phpfunction:`json_decode` functions. + +It can be useful to modify how these functions operate in certain instances +by providing options such as ``JSON_PRESERVE_ZERO_FRACTION``. You can use +the serialization context to pass in these options using the key +``json_encode_options`` or ``json_decode_options`` respectively:: + + $this->serializer->serialize($data, 'json', [ + 'json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION, + ]); + +All context options available for the JSON encoder are: + +``json_decode_associative`` (default: ``false``) + If set to ``true`` returns the result as an array, returns a nested ``stdClass`` hierarchy otherwise. +``json_decode_detailed_errors`` (default: ``false``) + If set to ``true`` exceptions thrown on parsing of JSON are more specific. Requires `seld/jsonlint`_ package. + + .. versionadded:: 6.4 + + The ``json_decode_detailed_errors`` option was introduced in Symfony 6.4. +``json_decode_options`` (default: ``0``) + Flags passed to :phpfunction:`json_decode` function. +``json_encode_options`` (default: ``\JSON_PRESERVE_ZERO_FRACTION``) + Flags passed to :phpfunction:`json_encode` function. +``json_decode_recursion_depth`` (default: ``512``) + Sets maximum recursion depth. + +The ``CsvEncoder`` +------------------ + +The ``CsvEncoder`` encodes to and decodes from CSV. Serveral :ref:`context options ` +are available to customize the behavior of the encoder: + +``csv_delimiter`` (default: ``,``) + Sets the field delimiter separating values (one character only). +``csv_enclosure`` (default: ``"``) + Sets the field enclosure (one character only). +``csv_end_of_line`` (default: ``\n``) + Sets the character(s) used to mark the end of each line in the CSV file. +``csv_escape_char`` (default: empty string) + Sets the escape character (at most one character). +``csv_key_separator`` (default: ``.``) + Sets the separator for array's keys during its flattening +``csv_headers`` (default: ``[]``, inferred from input data's keys) + Sets the order of the header and data columns. + E.g. if you set it to ``['a', 'b', 'c']`` and serialize + ``['c' => 3, 'a' => 1, 'b' => 2]``, the order will be ``a,b,c`` instead + of the input order (``c,a,b``). +``csv_escape_formulas`` (default: ``false``) + Escapes fields containing formulas by prepending them with a ``\t`` character. +``as_collection`` (default: ``true``) + Always returns results as a collection, even if only one line is decoded. +``no_headers`` (default: ``false``) + Setting to ``false`` will use first row as headers when denormalizing, + ``true`` generates numeric headers. +``output_utf8_bom`` (default: ``false``) + Outputs special `UTF-8 BOM`_ along with encoded data. + +The ``XmlEncoder`` +------------------ + +This encoder transforms PHP values into XML and vice versa. + +For example, take an object that is normalized as following:: + + $normalizedArray = ['foo' => [1, 2], 'bar' => true]; + +The ``XmlEncoder`` will encode this object like: + +.. code-block:: xml + + + + 1 + 2 + 1 + + +The special ``#`` key can be used to define the data of a node:: + + ['foo' => ['@bar' => 'value', '#' => 'baz']]; + + /* is encoded as follows: + + + + baz + + + */ + +Furthermore, keys beginning with ``@`` will be considered attributes, and +the key ``#comment`` can be used for encoding XML comments:: + + $encoder = new XmlEncoder(); + $xml = $encoder->encode([ + 'foo' => ['@bar' => 'value'], + 'qux' => ['#comment' => 'A comment'], + ], 'xml'); + /* will return: + + + + + + */ + +You can pass the context key ``as_collection`` in order to have the results +always as a collection. + +.. note:: + + You may need to add some attributes on the root node:: + + $encoder = new XmlEncoder(); + $encoder->encode([ + '@attribute1' => 'foo', + '@attribute2' => 'bar', + '#' => ['foo' => ['@bar' => 'value', '#' => 'baz']] + ], 'xml'); + + // will return: + // + // + // baz + // + +.. tip:: + + XML comments are ignored by default when decoding contents, but this + behavior can be changed with the optional context key ``XmlEncoder::DECODER_IGNORED_NODE_TYPES``. + + Data with ``#comment`` keys are encoded to XML comments by default. This can be + changed by adding the ``\XML_COMMENT_NODE`` option to the ``XmlEncoder::ENCODER_IGNORED_NODE_TYPES`` + key of the ``$defaultContext`` of the ``XmlEncoder`` constructor or + directly to the ``$context`` argument of the ``encode()`` method:: + + $xmlEncoder->encode($array, 'xml', [XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_COMMENT_NODE]]); + +The ``XmlEncoder`` Context Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These are the options available on the :ref:`serializer context `: + +``xml_format_output`` (default: ``false``) + If set to true, formats the generated XML with line breaks and indentation. +``xml_version`` (default: ``1.0``) + Sets the XML version attribute. +``xml_encoding`` (default: ``utf-8``) + Sets the XML encoding attribute. +``xml_standalone`` (default: ``true``) + Adds standalone attribute in the generated XML. +``xml_type_cast_attributes`` (default: ``true``) + This provides the ability to forget the attribute type casting. +``xml_root_node_name`` (default: ``response``) + Sets the root node name. +``as_collection`` (default: ``false``) + Always returns results as a collection, even if only one line is decoded. +``decoder_ignored_node_types`` (default: ``[\XML_PI_NODE, \XML_COMMENT_NODE]``) + Array of node types (`DOM XML_* constants`_) to be ignored while decoding. +``encoder_ignored_node_types`` (default: ``[]``) + Array of node types (`DOM XML_* constants`_) to be ignored while encoding. +``load_options`` (default: ``\LIBXML_NONET | \LIBXML_NOBLANKS``) + XML loading `options with libxml`_. +``save_options`` (default: ``0``) + XML saving `options with libxml`_. + + .. versionadded:: 6.3 + + The ``save_options`` option was introduced in Symfony 6.3. +``remove_empty_tags`` (default: ``false``) + If set to ``true``, removes all empty tags in the generated XML. +``cdata_wrapping`` (default: ``true``) + If set to ``false``, will not wrap any value containing one of the + following characters ( ``<``, ``>``, ``&``) in `a CDATA section`_ like + following: ````. + + .. versionadded:: 6.4 + + The ``cdata_wrapping`` option was introduced in Symfony 6.4. + +Example with a custom ``context``:: + + use Symfony\Component\Serializer\Encoder\XmlEncoder; + + $data = [ + 'id' => 'IDHNQIItNyQ', + 'date' => '2019-10-24', + ]; + + $xmlEncoder->encode($data, 'xml', ['xml_format_output' => true]); + // outputs: + // + // + // IDHNQIItNyQ + // 2019-10-24 + // + + $xmlEncoder->encode($data, 'xml', [ + 'xml_format_output' => true, + 'xml_root_node_name' => 'track', + 'encoder_ignored_node_types' => [ + \XML_PI_NODE, // removes XML declaration (the leading xml tag) + ], + ]); + // outputs: + // + // IDHNQIItNyQ + // 2019-10-24 + // + +The ``YamlEncoder`` +------------------- + +This encoder requires the :doc:`Yaml Component ` and +transforms from and to Yaml. + +Like other encoder, several :ref:`context options ` are +available: + +``yaml_inline`` (default: ``0``) + The level where you switch to inline YAML. +``yaml_indent`` (default: ``0``) + The level of indentation (used internally). +``yaml_flags`` (default: ``0``) + A bit field of ``Yaml::DUMP_*``/``Yaml::PARSE_*`` constants to + customize the encoding/decoding YAML string. + +.. _serializer-custom-encoder: + +Creating a Custom Encoder +------------------------- + +Imagine you want to serialize and deserialize `NEON`_. For that you'll have to +create your own encoder:: + + // src/Serializer/YamlEncoder.php + namespace App\Serializer; + + use Nette\Neon\Neon; + use Symfony\Component\Serializer\Encoder\DecoderInterface; + use Symfony\Component\Serializer\Encoder\EncoderInterface; + + class NeonEncoder implements EncoderInterface, DecoderInterface + { + public function encode($data, string $format, array $context = []) + { + return Neon::encode($data); + } + + public function supportsEncoding(string $format) + { + return 'neon' === $format; + } + + public function decode(string $data, string $format, array $context = []) + { + return Neon::decode($data); + } + + public function supportsDecoding(string $format) + { + return 'neon' === $format; + } + } + +.. tip:: + + If you need access to ``$context`` in your ``supportsDecoding`` or + ``supportsEncoding`` method, make sure to implement + ``Symfony\Component\Serializer\Encoder\ContextAwareDecoderInterface`` + or ``Symfony\Component\Serializer\Encoder\ContextAwareEncoderInterface`` accordingly. + +Registering it in Your App +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you use the Symfony Framework, then you probably want to register this encoder +as a service in your app. If you're using the +:ref:`default services.yaml configuration `, +that's done automatically! + +If you're not using :ref:`autoconfigure `, make sure +to register your class as a service and tag it with ``serializer.encoder``: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + # ... + + App\Serializer\NeonEncoder: + tags: ['serializer.encoder'] + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\Serializer\NeonEncoder; + + return function(ContainerConfigurator $container) { + // ... + + $services->set(NeonEncoder::class) + ->tag('serializer.encoder') + ; + }; + +Now you'll be able to serialize and deserialize NEON! + +.. _JSON: https://www.json.org/json-en.html +.. _XML: https://www.w3.org/XML/ +.. _YAML: https://yaml.org/ +.. _CSV: https://tools.ietf.org/html/rfc4180 +.. _seld/jsonlint: https://github.com/Seldaek/jsonlint +.. _`UTF-8 BOM`: https://en.wikipedia.org/wiki/Byte_order_mark +.. _`DOM XML_* constants`: https://www.php.net/manual/en/dom.constants.php +.. _`options with libxml`: https://www.php.net/manual/en/libxml.constants.php +.. _`a CDATA section`: https://en.wikipedia.org/wiki/CDATA +.. _NEON: https://ne-on.org/ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy