From 7be3c936747f67a318e6049019a00bb5c6581494 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Sun, 6 Mar 2022 14:36:27 +0100 Subject: [PATCH] [Serializer] Add context builders documentation --- components/serializer.rst | 35 +++++++++++ serializer.rst | 55 ++++++++++++++-- serializer/custom_context_builders.rst | 86 ++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 serializer/custom_context_builders.rst diff --git a/components/serializer.rst b/components/serializer.rst index fc0d32c2531..6adf5db65de 100644 --- a/components/serializer.rst +++ b/components/serializer.rst @@ -1202,6 +1202,41 @@ Option Description Defaul to customize the encoding / decoding YAML string =============== ======================================================== ========================== +.. _component-serializer-context-builders: + +Context Builders +---------------- + +Context builders are objects that help creating the :ref:`serialization context `. + +You can easily use context builders by instantiating them:: + + 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->toArray()) + ->withDelimiter(';'); + + $serializer->serialize($something, 'csv', $contextBuilder->toArray()); + +.. note:: + + The Serializer component provides a context builder + for each :ref:`normalizer ` + and :ref:`encoder `. + + You can also create custom context builders to deal with your + context values. Read more at :doc:`/serializer/custom_context_builders`. + Skipping ``null`` Values ------------------------ diff --git a/serializer.rst b/serializer.rst index ef4a8e483df..c9f42cd2751 100644 --- a/serializer.rst +++ b/serializer.rst @@ -89,6 +89,8 @@ possible to set the priority of the tag in order to decide the matching order. ``DateTime`` or ``DateTimeImmutable`` classes to avoid excessive memory usage and exposing internal details. +.. _serializer-context: + Serializer Context ------------------ @@ -149,6 +151,46 @@ configuration: ; }; +.. _serializer-using-context-builders: + +Using Context Builders +---------------------- + +To define a proper (de)serialization context, you can leverage context builders. +Those are objects that help you to create that context by providing +auto-completion, validation, and documentation:: + + use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder; + + $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 full (de)serialization context, you will be able to chain them using the +``withContext`` method. As the ``withContext`` method takes an array as an argument, it is +also possible to pass custom values to that context:: + + 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->toArray()) + ->withDelimiter(';'); + + $serializer->serialize($something, 'csv', $contextBuilder->toArray()); + +If you want auto-completion, validation, and documentation for your custom context values, +you can :doc:`create your context builders `. + .. _serializer-using-serialization-groups-annotations: Using Serialization Groups Annotations @@ -197,11 +239,13 @@ to your class:: You can now choose which groups to use when serializing:: - $json = $serializer->serialize( - $product, - 'json', - ['groups' => 'show_product'] - ); + use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder; + + $context = (new ObjectNormalizerContextBuilder()) + ->withGroups('show_product') + ->toArray(); + + $json = $serializer->serialize($product, 'json', $context); .. tip:: @@ -293,6 +337,7 @@ take a look at how this bundle works. serializer/custom_encoders serializer/custom_normalizer + serializer/custom_context_builders .. _`API Platform`: https://api-platform.com .. _`JSON-LD`: https://json-ld.org diff --git a/serializer/custom_context_builders.rst b/serializer/custom_context_builders.rst new file mode 100644 index 00000000000..2bf31680968 --- /dev/null +++ b/serializer/custom_context_builders.rst @@ -0,0 +1,86 @@ +.. index:: + single: Serializer; Custom context builders + +How to Create your Custom Context Builder +========================================= + +The :doc:`Serializer Component ` uses Normalizers +and Encoders to transform any data to any data-structure (e.g. JSON). +That serialization process could be configured thanks to a +:ref:`serialization context `, which can be built thanks to +:ref:`context builders `. + +Each built-in normalizer/encoder has its related context builder. +But, as an example, you may want to use custom context values +for your :doc:`custom normalizers ` +and create a custom context builder related to them. + +Creating a new context builder +------------------------------ + +Let's imagine that you want to handle date denormalization differently if they +are coming from a legacy system, by converting them to ``null`` if the serialized +value is ``0000-00-00``. To do that you'll first have to create your normalizer:: + + // src/Serializer/ZeroDateTimeDenormalizer.php + namespace App\Serializer; + + use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; + use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; + use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + + final class ZeroDateTimeDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface + { + use DenormalizerAwareTrait; + + public function denormalize($data, string $type, string $format = null, array $context = []) + { + if ('0000-00-00' === $data) { + return null; + } + + unset($context['zero_datetime_to_null']); + + return $this->denormalizer->denormalize($data, $type, $format, $context); + } + + public function supportsDenormalization($data, string $type, string $format = null, array $context = []) + { + return true === ($context['zero_datetime_to_null'] ?? false) + && is_a($type, \DateTimeInterface::class, true); + } + } + +You'll therefore be able to cast zero-ish dates to ``null`` during denormalization:: + + $legacyData = '{"updatedAt": "0000-00-00"}'; + + $serializer->deserialize($legacyData, MyModel::class, 'json', ['zero_datetime_to_null' => true]); + +Then, if you don't want other developers to have to remind the precise ``zero_date_to_null`` context key, +you can create a dedicated context builder:: + + // src/Serializer/LegacyContextBuilder + namespace App\Serializer; + + use Symfony\Component\Serializer\Context\ContextBuilderTrait; + + final class LegacyContextBuilder + { + use ContextBuilderTrait; + + public function withLegacyDates(bool $legacy): static + { + return $this->with('zero_datetime_to_null', $legacy); + } + } + +And finally use it to build the serialization context:: + + $legacyData = '{"updatedAt": "0000-00-00"}'; + + $context = (new LegacyContextBuilder()) + ->withLegacyDates(true) + ->toArray(); + + $serializer->deserialize($legacyData, MyModel::class, 'json', $context);