Skip to content

[Serializer] Add context builders documentation #16591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions components/serializer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <serializer-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 <component-serializer-normalizers>`
and :ref:`encoder <component-serializer-encoders>`.

You can also create custom context builders to deal with your
context values. Read more at :doc:`/serializer/custom_context_builders`.

Skipping ``null`` Values
------------------------

Expand Down
55 changes: 50 additions & 5 deletions serializer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------

Expand Down Expand Up @@ -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 <component-serializer-context-builders>`.
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/custom_context_builders>`.

.. _serializer-using-serialization-groups-annotations:

Using Serialization Groups Annotations
Expand Down Expand Up @@ -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::

Expand Down Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions serializer/custom_context_builders.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.. index::
single: Serializer; Custom context builders

How to Create your Custom Context Builder
=========================================

The :doc:`Serializer Component </components/serializer>` 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 <serializer-context>`, which can be built thanks to
:ref:`context builders <component-serializer-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 </serializer/custom_normalizer>`
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);