Как использовать Serializer

Дата обновления перевода 2025-02-18

Как использовать Serializer

Symfony предоставляет сериализатор для преобразования структур данных из
одного формата в PHP-объекты и наоборот.

Это чаще всего используется при создании API или общении со сторонними API.
Сериализатор может преобразовать входящий JSON-запрос в PHP-объект, который потребляется вашим приложением. Затем, при генерировании ответа, вы можете использовать сериализатор для преобразования
PHP-объектов обратно в ответ JSON.

Его также можно использовать, например, для загрузки данных
конфигурации CSV как PHP-объектов, или даже для преобразования между
форматами (например, YAML в XML).

Установка

В приложениях, использующих Symfony Flex , выполните эту команду, чтобы установить пакет Symfony serializer перед его использованием:

1
$ composer require symfony/serializer-pack

Note

Пакет сериализатора также устанавливает некоторые часто используемые дополнительные зависимости компонента Serializer. При использовании этого компонента вне фреймворка Symfony, вы можете захотеть начать с пакета symfony/serializer и установить дополнительные зависимости, если они вам нужны.

See also

Популярной альтернативой компоненту Symfony Serializer является сторонняя библиотека JMS serializer.

Сериализация объекта

Для этого примера предположим, что в вашем проекте существует следующий класс:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Model/Person.php
namespace App\Model;

class Person
{
    public function __construct(
        private int $age,
        private string $name,
        private bool $sportsperson
    ) {
    }

    public function getAge(): int
    {
        return $this->age;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function isSportsperson(): bool
    {
        return $this->sportsperson;
    }
}

Если вы хотите преобразовать объекты этого типа в JSON-структуру (например
отправить их через API-ответ), получите сервис serializer, используя тип параметра SerializerInterface :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/PersonController.php
namespace App\Controller;

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;

class PersonController extends AbstractController
{
    public function index(SerializerInterface $serializer): Response
    {
        $person = new Person('Jane Doe', 39, false);

        $jsonContent = $serializer->serialize($person, 'json');
        // $jsonContent содержит {"name":"Jane Doe","age":39,"sportsperson":false}

        return JsonResponse::fromJsonString($jsonContent);
    }
}

Первый параметр serialize() является объектом, который нужно сериализировать, а второй - используется для выбора соответствующего кодировщика (то есть формата), в этом случае - JsonEncoder.

Tip

Когда ваш класс контроллера расширяет AbstractController (как в вышеприведенном примере), вы можете
упростить свой контроллер, используя метод json() для создания JSON-ответа из объекта с помощью Serializer:

1
2
3
4
5
6
7
8
9
10
class PersonController extends AbstractController
{
    public function index(): Response
    {
        $person = new Person('Jane Doe', 39, false);

        // если Serializer недоступен, будет использоваться json_encode()
        return $this->json($person);
    }
}

Использование Serializer в шаблонах Twig

Вы также можете сериализировать объекты в любом шаблоне Twig с помощью фильтра serialize:

1
{{ person|serialize(format = 'json') }}

Смотрите справочник twig , чтобы узнать больше.

Десериализация объекта

API часто также требуют преобразования отформатированного тела запроса (например, JSON) в объект PHP. Этот процесс называется десериализация (также известный как "гидратация"):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Controller/PersonController.php
namespace App\Controller;

// ...
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;

class PersonController extends AbstractController
{
    // ...

    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');

        // ... сделать что-то с $person и вернуть ответ
        }
}

В этом случае, deserialize() требует трех параметров:

  1. Данные, которые нужно расшифровать
  2. Имя класса, в который будет расшифрована эта информация
  3. Название кодировщика, который используется для преобразования данных в массив (то есть формат ввода)

При отправке запроса к этому контроллеру (например {«first_name»: «John Doe», «age»:54, «sportsperson»:true}), сериализатор создаст новый экземпляр Person и установит свойства в значения из предоставленного JSON.

Note

По умолчанию, дополнительные атрибуты, которые не сопоставлены с денормализованным объектом, будут проигнорированы компонентом Serializer. Например,
если запрос к вышеприведенному контроллеру содержит {..., «city»: «Paris"}, поле city будет проигнорировано. Вы также можете вызвать исключение в этих случаях с помощью контекста serializer , о котором вы узнаете позже.

See also

Вы также можете десериализовать данные в существующий экземпляр объекта (например при обновлении данных). Смотрите
Десериализация в существующий объект .

Процесс сериализации: нормализаторы и кодировщики

Сериализатор использует двухэтапный процесс при (де)сериализации объектов:

В обоих направлениях данные всегда сначала преобразуются в массив. Это разделяет процесс на две отдельные задачи:

Нормализаторы
Эти классы преобразуют объекты в массивы и наоборот. Они выполняют тяжелую работу, выясняя, какие свойства класса должны быть сериализированы, какое значение они имеют и какое название они должны иметь.
Кодировщики
Кодировщики преобразуют массивы в определенный формат и наоборот.
Каждый кодировщик точно знает, как анализировать и генерировать определенный формат, например JSON или XML.

Внутренне класс Serializer использует отсортированный список нормализаторов и один кодировщик для конкретного формата при (де)сериализации объекта.

Существует несколько нормализаторов, сконфигурированных в сервисе Serializer по умолчанию. Самым важным нормализатором является ObjectNormalizer. Этот нормализатор использует отображение и компонент PropertyAccess` для преобразования между любым объектом и массивом. Вы узнаете больше об этом и других нормализаторах позже.

Сериализатор по умолчанию также сконфигурирован с некоторыми кодировщиками, которые охватывают распространенные форматы, используемые HTTP-приложениями:

Прочтите больше об этих кодировщиках и их конфигурации в Кодировщики Serializer.

Tip

Проэкт API Platform предоставляет кодировщики для более продвинутых форматов:

Контекст Serializer

Сериализатор, его нормализаторы и кодировщики конфигурируются с помощью контекста
сериализатора
. Этот контекст можно сконфигурировать в нескольких местах:

  • Глобально через конфигурацию фреймворка
  • Во время сериализации/десериализации `
  • Для конкретного свойства .

Вы можете использовать все три варианта одновременно. Если одна и та же настройка сконфигурирована в нескольких местах, последняя в списке выше будет переопределять предыдущую (например, настройка для конкретного свойства переопределяет то, что
сконфигурировано глобально).

Сконфигурируйте контекст по умолчанию

Вы можете сконфигурировать контекст по умолчанию в конфигурации фреймворка,
например, запретить дополнительные поля при десериализации:

1
2
3
4
5
# config/packages/serializer.yaml
framework:
    serializer:
        default_context:
            allow_extra_attributes: false

Передайте контекст во время сериализации/десериализации

Вы также можете сконфигурировать контекст для одного вызова serialize()/ deserialize(). Например, вы можете пропустить свойства со значением null только для одного вызова сериализатора:

1
2
3
4
5
6
7
8
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;

// ...
$serializer->serialize($person, 'json', [
    AbstractObjectNormalizer::SKIP_NULL_VALUES => true
]);

// следующие вызовы к serialize() НЕ будут пропускать значения null
Использование строителей контекста

Чтобы определить контекст (де)сериализации, вы можете использовать "строителей контекста", которые являются объектами, которые помогают вам создавать этот контекст, предоставляя автозаполнение, валидацию и документацию:

1
2
3
4
5
use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder;

$contextBuilder = (new DateTimeNormalizerContextBuilder())
    ->withFormat('Y-m-d H:i:s');
$serializer->serialize($something, 'json', $contextBuilder->toArray());

Каждый нормализатор/кодировщик имеет свой связанный строитель контекста . Чтобы создать более сложный контекст (де)сериализации, вы можете сделать из них цепочку, используя метод withContext():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());

See also

Вы также можете создать собственных строителей контекста, чтобы иметь автозаполнение, валидацию и документацию для ваших пользовательских значений контекста.

Сконфигурируйте контекст в конкретном свойстве

Наконец, вы также можете сконфигурировать значение контекста для конкретного свойства объекта. Например, для конфигурации формата времени и даты:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Model/Person.php

// ...
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;

class Person
{
    #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
    public \DateTimeImmutable $createdAt;

    // ...
}

Note

При использовании YAML или XML файлы карт должны быть размещены в одном из этих мест:

  • Все файлы *.yaml и *.xml - в каталоге config/serializer/.
  • Файл serialization.yaml или serialization.xml - в каталоге пакета Resources/config/.
  • Все файлы *.yaml и *.xml - в каталоге пакета Resources/config/serialization/.

Вы также можете указать контекст, специфичный для нормализации или денормализации:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Model/Person.php

// ...
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;

class Person
{
    #[Context(
        normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'],
        denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339],
    )]
    public \DateTimeImmutable $createdAt;

    // ...
}

Вы также можете ограничить использование контекста для некоторых групп :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Model/Person.php

// ...
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;

class Person
{
    #[Groups(['extended'])]
    #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
    #[Context(
        context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED],
        groups: ['extended'],
    )]
    public \DateTimeImmutable $createdAt;

    // ...
}

Атрибут может повторяться столько раз, сколько нужно, для одного свойства.
Контекст без группы всегда применяется первым. Затем контекст для соответствующих групп объединяется в предоставленном порядке.

Если вы повторяете один и тот же контекст в нескольких свойствах, воспользуйтесь атрибутом
#[Context] в вашем классе, чтобы применить эту конфигурацию контекста ко всем свойствам класса :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
{
    // ...
}

Сериализация в или из PHP-массивов

Serializer по умолчанию также может быть
использован для выполнения только одного шага двухшагового процесса сериализации с помощью соответствующего интерфейса:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);

        // использовать normalize(), чтобы преобразовать PHP-объект в массив
        $personArray = $serializer->normalize($person, 'json');

        // ...и denormalize(), чтобы преобразовать массив обратно в PHP-объект
        $personCopy = $serializer->denormalize($personArray, Person::class);

        // ...
    }

    public function json(DecoderInterface&EncoderInterface $serializer): Response
    {
        $data = ['name' => 'Jane Doe'];

        // использовать encode(), чтобы преобразовать PHP-массивы в другой формат
        $json = $serializer->encode($data, 'json');

        // ...и decode(), чтобы преобразовать любой формат просто в PHP-массивы (вместо объектов)
        $data = $serializer->decode('{"name":"Charlie Doe"}', 'json');
        // $data contains ['name' => 'Charlie Doe']
    }
}

Игнорирование свойств

ObjectNormalizer нормализует все свойства объекта и все
методы, начинающиеся с get*(), has*(), is*() и can*(). Некоторые свойства или методы никогда не должны быть сериализованы. Вы можете
исключить их с помощью атрибута #[Ignore]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Model/Person.php
namespace App\Model;

use Symfony\Component\Serializer\Attribute\Ignore;

class Person
{
    // ...

    #[Ignore]
    public function isPotentiallySpamUser(): bool
    {
        // ...
    }
}

Свойство potentiallySpamUser теперь никогда не будет сериализована:

1
2
3
4
5
6
7
8
9
10
11
12
13
use App\Model\Person;

// ...
$person = new Person('Jane Doe', 32, false);
$json = $serializer->serialize($person, 'json');
// $json містить {"name":"Jane Doe","age":32,"sportsperson":false}

$person1 = $serializer->deserialize(
    '{"name":"Jane Doe","age":32,"sportsperson":false","potentiallySpamUser":false}',
    Person::class,
    'json'
);
// значення "potentiallySpamUser" ігнорується

Игнорирование атрибутов с использованием контекста

Вы также можете передать массив имен атрибутов для игнорирования во время выполнения с помощью
опций контекста ignored_attributes:

1
2
3
4
5
6
7
8
9
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;

// ...
$person = new Person('Jane Doe', 32, false);
$json = $serializer->serialize($person, 'json',
[
    AbstractNormalizer::IGNORED_ATTRIBUTES => ['age'],
]);
// $json содержит {"name":"Jane Doe","sportsperson":false}

Однако это может быстро привести к непредсказуемым последствиям, если использовать его чрезмерно. См. следующий раздел о группах сериализации для лучшего решения.

Выбор конкретных свойств

Вместо того, чтобы исключать свойство или метод во всех ситуациях, вам может потребоваться исключить некоторые свойства в одном месте, но сериализовать их в другом. Группы - удобный способ достичь этого.

Вы можете добавить атрибут #[Groups] к вашему классу:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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;

    // ...
}

Теперь вы можете выбрать, какие группы использовать при сериализации:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$json = $serializer->serialize(
    $person,
    'json',
    ['groups' => 'public-view']
);
// $json содержит {"name":"Jane Doe","sportsperson":false}

// вы также можете передать массив групп
$json = $serializer->serialize(
    $person,
    'json',
    ['groups' => ['public-view', 'admin-view']]
);
// $json содержит {"name":"Jane Doe","age":32,"sportsperson":false}

// или использовать специальное значение «*» для выбора всех групп
$json = $serializer->serialize(
    $person,
    'json',
    ['groups' => '*']
);
// $json содержит {"name":"Jane Doe","age":32,"sportsperson":false}

Использование контекста сериализации

Наконец, вы также можете воспользоваться опцией контекста attributes для выбора свойств во время выполнения:

1
2
3
4
5
6
7
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
// ...

$json = $serializer->serialize($person, 'json', [
    AbstractNormalizer::ATTRIBUTES => ['name', 'company' => ['name']]
]);
// $json содержит {"name":"Dunglas","company":{"name":"Les-Tilleuls.coop"}}

Доступны только атрибуты, которые не игнорируются .
Если установлены группы сериализации, можно использовать только атрибуты, разрешенные этими группами.

Работа с массивами

Сериализатор может работать с массивами объектов. Сериализация массивов работает так же, как и сериализация одного объекта:

1
2
3
4
5
6
7
8
9
10
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 содержит [{"name":"Jane Doe","age":39,"sportsman":false},{"name":"John Smith","age":52,"sportsman":true}]

Чтобы десериализировать список объектов, к типу параметра нужно добавить []:

1
2
3
4
// ...

$jsonData = ...; // сериализованные данные JSON из предыдущего примера
$persons = $serializer->deserialize($JsonData, Person::class.'[]', 'json');

Для вложенных классов вы должны добавить тип PHPDoc к свойству, конструктору или сеттеру:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Model/UserGroup.php
namespace App\Model;

class UserGroup
{
    /**
     * @param Person[] $members
     */
    public function __construct(
        private array $members,
    ) {
    }

    // или, если вы используете сеттер

    /**
     * @param Person[] $members
     */
    public function setMembers(array $members): void
    {
        $this->members = $members;
    }

    // ...
}

Tip

Serializer также поддерживает типы массивов, которые используются в статическом анализе, такие как list<Person> и array<Person>. Убедитесь, что установлены пакеты phpstan/phpdoc-parser и phpdocumentor/reflection-docblock (они являются частью
пакета symfony/serializer-pack).

Десериализация с использованием вложенных структур

Некоторые API могут предоставлять многословные вложенные структуры, которые вы захотите упростить в объекты PHP. Например, представьте себе следующий ответ в формате JSON:

1
2
3
4
5
6
7
8
9
{
    "id": "123",
    "profile": {
        "username": "jdoe",
        "personal_information": {
            "full_name": "Jane Doe"
        }
    }
}

Возможно, вы захотите сериализировать эту информацию в один объект PHP типа:

1
2
3
4
5
6
class Person
{
    private int $id;
    private string $username;
    private string $fullName;
}

Используйте #[SerializedPath], чтобы указать путь к вложенному свойству, используя валидный синтаксис PropertyAccess:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}

Warning

SerializedPath нельзя использовать в сочетании с SerializedName для одного свойства.

Атрибут #[SerializedPath] также применяется к сериализации PHP-объекта:

1
2
3
4
5
6
use App\Model\Person;
// ...

$person = new Person(123, 'jdoe', 'Jane Doe');
$jsonContent = $serializer->serialize($person, 'json');
// $jsonContent містить {"id":123,"profile":{"username":"jdoe","personal_information":{"full_name":"Jane Doe"}}}

Преобразование имен свойств при сериализации и десериализации

Иногда сериализированные атрибуты нужно называть иначе, чем свойства или методы геттеров/сеттеров PHP-классов. Этого можно с помощью преобразователей имен.

Сервис сериализатора использует
MetadataAwareNameConverter. С помощью этого преобразователя имен вы можете изменить имя атрибута, используя атрибут #[SerializedName]:

1
2
3
4
5
6
7
8
9
10
11
12
// src/Model/Person.php
namespace App\Model;

use Symfony\Component\Serializer\Attribute\SerializedName;

class Person
{
    #[SerializedName('customer_name')]
    private string $name;

    // ...
}

Это пользовательское отображение используется для преобразования имен свойств во время сериализации и десериализации объектов:

1
2
3
4
// ...

$json = $serializer->serialize($person, 'json');
// $json містить {"customer_name":"Jane Doe", ...}

See also

Вы также можете создать пользовательский класс преобразователя имен. Подробнее об этом можно прочитать в в Как создать пользовательский преобразователь имен.

CamelCase в snake_case

Во многих форматах принято использовать нижнее подчеркивание для разделения слов
(также известно как snake_case). Однако в приложениях Symfony принято использовать
camelCase для именования свойств.

Symfony предоставляет встроенный преобразователь имен, предназначенный для преобразования между стилями snake_case и CamelCase при сериализации и десериализации.
Вы можете использовать его вместо преобразователя имен, учитывающего метаданные,
установив настройку name_converter в значение serializer.name_converter.camel_case_to_snake_case:

1
2
3
4
# config/packages/serializer.yaml
framework:
    serializer:
        name_converter: 'serializer.name_converter.camel_case_to_snake_case'

Нормализаторы Serializer

По умолчанию сервис сериализатора сконфигурирован с такими нормализаторами
(в порядке приоритета):

UnwrappingDenormalizer
Можно использовать для денормализации только части ввода, подробнее об об этом далее в этой статье .
ProblemNormalizer
Нормализует ошибки FlattenException согласно спецификации проблем API RFC 7807.
UidNormalizer

Нормализует объекты, которые расширяют AbstractUid.

Формат нормализации по умолчанию для объектов, реализующих Uuid - это формат RFC 4122 (пример: d9e7a184-5d5b-11ea-a62a-3499710062d0). Формат нормализации по умолчанию для объектов, реализующих Ulid - это формат Base 32 (пример: 01E439TP9XJZ9RPFH3T1PYBCR8). Вы можете изменить формат строки, задав опцию контекста сериализатора UidNormalizer::NORMALIZATION_FORMAT_KEY как UidNormalizer::NORMALIZATION_FORMAT_BASE_58, UidNormalizer::NORMALIZATION_FORMAT_BASE_32 или UidNormalizer::NORMALIZATION_FORMAT_RFC_4122.

Также он может денормализовать строки uuid или ulid до Uuuuid` или Ulid. Формат не имеет значения.

DateTimeNormalizer

Это нормализует между объектами DateTimeInterface (например DateTime и DateTimeImmutable) и строками, целыми числами или числами с плавающей запятой. По умолчанию, он преобразует их в строки, используя формат RFC 3339.
Используйте DateTimeNormalizer::FORMAT_KEY и DateTimeNormalizer::TIMEZONE_KEY для изменения формата.

Для преобразования объектов в целые числа или числа с плавающей запятой, установите в сериализаторе опцию контекста DateTimeNormalizer::CAST_KEY в значение int или float.

7.1

Опция контекста DateTimeNormalizer::CAST_KEY была представлена в Symfony 7.1.

ConstraintViolationListNormalizer
Этот нормализатор преобразует объекты, реализующие ConstraintViolationListInterface, в список
ошибок в соответствии со стандартом RFC 7807.
DateTimeZoneNormalizer
Этот нормализатор преобразует между объектами DateTimeZone и строками, которые представляют название часового пояса согласно списку часовых поясов PHP.
DateIntervalNormalizer
Это нормализует между объектами и строками DateInterval. По умолчанию используется формат P%yY%mM%dDT%hH%iM%sS. Используйте опцию DateIntervalNormalizer::FORMAT_KEY, чтобы изменить его.
FormErrorNormalizer

Этот нормализатор работает с классами, которые реализуют FormInterface.

Он будет получать ошибки из формы и нормализовать их согласно спецификации
проблем API RFC 7807.

TranslatableNormalizer

Этот нормализатор преобразует объекты, реализующие TranslatableInterface в переведенную строку с помощью translator.

Вы можете определить локаль, которую следует использовать для перевода объекта, установив
опцию контекста TranslatableNormalizer::NORMALIZATION_LOCALE_KEY.

BackedEnumNormalizer

Этот нормализатор преобразует между исчислением BackedEnum и строками или целыми числами.

По умолчанию, если данные не являются валидным исчислением, вызывается исключение. Если вы вместо этого хотите получить null, вы можете установить опцию
BackedEnumNormalizer::ALLOW_INVALID_VALUES.

DataUriNormalizer
Этот нормализатор преобразует между объектами SplFileInfo и строкой данных URI (data:...) таким образом, что файлы могут быть встроены в сериализованные данные. Цей нормалізатор перетворює між об'єктами SplFileInfo та
JsonSerializableNormalizer

Этот нормализатор работает с классами, которые реализуют JsonSerializable.

Он вызовет метод JsonSerializable::jsonSerialize() и затем далее нормализует результат. Это означает, что вложенные классы JsonSerializable также будут нормализованы.

Этот нормализатор особенно полезен, когда вы хотите постепенно мигрировать с существующей кодовой базы с использованием простой json_encode на Symfony Serializer, позволяя вам смешивать нормализаторы для разных классов.

В отличие от json_encode, можно обрабатывать циклические ссылки.

ArrayDenormalizer
Этот денормализатор превращает массив массивов в массив объектов (с заданным типом). Смотрите Работа с массивами .
ObjectNormalizer

Это самый мощный нормализатор по умолчанию, используемый для любых объектов, которые не могут быть нормализованы другими нормализаторами.

Он использует Компонент PropertyAccess для чтения и записи в объекте. Это позволяет ему получать доступ к свойствам непосредственно или с помощью геттеров, сеттеров, хассеров, иссеров, каннеров,
добавителей и излучателей. Имена генерируются путем удаления get, set, has, is, add или remove из названия метода и преобразования первой буквы в строчную (например, getFirstName() -> firstName).

При денормализации поддерживаются использование конструктора, а также найденные методы.

Danger

Всегда проверяйте, чтобы DateTimeNormalizer было зарегистрировано во время сериализации классов DateTime или DateTimeImmutable, чтобы избежать чрезмерного использования памяти и раскрытия внутренних данных.

Встроенные нормализаторы

Кроме нормализаторов, зарегистрированных по умолчанию (см. предыдущий раздел), компонент serializer также предоставляет некоторые дополнительные нормализаторы. Вы можете зарегистрировать их, определив сервис и обозначив его тегом serializer.normalizer . Например, для использования CustomNormalizer вам следует определить сервис вроде:

1
2
3
4
5
6
7
8
9
# config/services.yaml
services:
    # ...

    # если вы используете автоконфигурацию, тег будет применен автоматически
    Symfony\Component\Serializer\Normalizer\CustomNormalizer:
        tags:
            # зарегистрировать нормализатор с высоким приоритетом (вызывается раньше)
            - { name: 'serializer.normalizer', priority: 500 }
CustomNormalizer
Этот нормализатор вызывает метод в объекте PHP при нормализации. PHP-объект должен реализовывать NormalizableInterface и/или DenormalizableInterface.
GetSetMethodNormalizer

Этот нормализатор является альтернативой стандартному ObjectNormalizer. Он читает содержание класса, вызывая "геттеры" (публичные методы, начинающиеся с get, has, is или can). Он будет денормализовать данные, вызывая конструктор и "сеттеры" (общедоступные методы, начинающиеся с set).

Объекты нормализуются к карте имен и значений (имена генерируются путем удаления префикса get из имени метода и преобразования первой буквы в строчную; например, getFirstName() -> firstName).

PropertyNormalizer

Это еще одна альтернатива ObjectNormalizer. Этот нормализатор напрямую читает и записывает публичные свойства, а также частные и защищенные свойства (как из класса, так и из всех его родительских классов), используя PHP-отражение. Он поддерживает вызов конструктора во время процесса денормализации.

Объекты нормализуются в карту имен свойств в значение свойств.

Вы также можете ограничить нормализатор, чтобы он использовал только свойства с определенной видимостью (например, только публичные свойства) с помощью опции контекста PropertyNormalizer::NORMALIZE_VISIBILITY. Вы можете установить любую комбинацию констант PropertyNormalizer::NORMALIZE_PUBLIC, PropertyNormalizer::NORMALIZE_PROTECTED и PropertyNormalizer::NORMALIZE_PRIVATE:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
// ...

$json = $serializer->serialize($person, 'json', [
    // серіалізувати лише публічні властивості
    PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC,

    // серіалізувати публічні та захищені властивості
    PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED,
]);

Отладка Serializer

Воспользуйтесь командой debug:serializer для сброса метаданных сериализатора для заданного класса:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ php bin/console debug:serializer 'App\Entity\Book'

    App\Entity\Book
    ---------------

    +-------------+------------------------------------------------------------+
    | Свойство    | Опции                                                      |
    +-------------+------------------------------------------------------------+
    | name        | [                                                          |
    |             |   "groups" => [                                            |
    |             |       "book:read",                                         |
    |             |       "book:write",                                        |
    |             |   ],                                                       |
    |             |   "maxDepth" => 1,                                         |
    |             |   "serializedName" => "book_name",                         |
    |             |   "serializedPath" => null,                                |
    |             |   "ignore" => false,                                       |
    |             |   "normalizationContexts" => [],                           |
    |             |   "denormalizationContexts" => []                          |
    |             | ]                                                          |
    | isbn        | [                                                          |
    |             |   "groups" => [                                            |
    |             |       "book:read",                                         |
    |             |   ],                                                       |
    |             |   "maxDepth" => null,                                      |
    |             |   "serializedName" => null,                                |
    |             |   "serializedPath" => "[data][isbn]",                      |
    |             |   "ignore" => false,                                       |
    |             |   "normalizationContexts" => [],                           |
    |             |   "denormalizationContexts" => []                          |
    |             | ]                                                          |
    +-------------+------------------------------------------------------------+

Продвинутая сериализация

Пропуск значений null

По умолчанию, Serializer будет сохранять свойства, содержащие значение null. Вы можете изменить это поведение, установив опцию контекста AbstractObjectNormalizer::SKIP_NULL_VALUES в значение true:

1
2
3
4
5
6
7
8
9
10
class Person
{
    public string $name = 'Jane Doe';
    public ?string $gender = null;
}

$jsonContent = $serializer->serialize(new Person(), 'json', [
    AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
]);
// $jsonContent содержит {"name":"Jane Doe"}

Работа с неинициализированными свойствами

В PHP типизированные свойства имеют состояние uninitialized, которое отличается. от стандартного null нетипизированных свойств. Когда вы пытаетесь получить доступ к типизированному свойству до того, как придать ему явное значение, вы получаете ошибку.

Чтобы избежать ошибки при сериализации или нормализации объекта с неинициализированными свойствами, по умолчанию ObjectNormalizer перехватывает эти ошибки и игнорирует следующие свойства.

Вы можете отключить такое поведение, установив опцию контекста AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES в значение false:

1
2
3
4
5
6
7
8
9
10
class Person {
    public string $name = 'Jane Doe';
    public string $phoneNumber; // uninitialized
}

$jsonContent = $normalizer->serialize(new Dummy(), 'json', [
    AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false,
]);
// вызывает Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException
// так как ObjectNormalizer не может прочитать неинициализированные свойства

Note

Использование PropertyNormalizer или GetSetMethodNormalizer с опцией контекста AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES, установленной как false, вызовент экземпляр \Error, если заданный объект имеет неинициализированные свойства, так как нормализаторы не могут их прочитать (напрямую или через методы геттера/иссера).

Рfбота c цикличными ссылками

Цикличные ссылки распространены при работе с ассоциированными объектами:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
    }
}

Чтобы избежать бесконечных циклов, нормализаторы вызывают исключение CircularReferenceException, когда встречается такой случай:

$organization = new Organization('Les-Tilleuls.coop'); $member = new Member('Kévin');

$organization->addMember($member); $member->setOrganization($organization);

$jsonContent = $serializer->serialize($organization, 'json'); // вызывает CircularReferenceException

Ключ circular_reference_limit в контексте задает количество раз, которые будет сериализирован тот же объект, прежде чем считать его цикличной ссылкой. Значение по умолчанию – 1.

Вместо того чтобы вызывать исключение, цикличные ссылки можно также обрабатывать с помощью пользовательских вызываемых. Это особенно полезно при сериализации сущностей с уникальными идентификаторами:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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).'".');
        }

        // сериализовать вложенную Organization только с именем (а не с членами)
        return $object->getName();
    },
];

$jsonContent = $serializer->serialize($organization, 'json', $context);
// $jsonContent содержит {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}

Работа с глубиной сериализации

Сериализатор может обнаруживать вложенные объекты одного класса и ограничивать глубину сериализации. Это полезно для древовидных структур, где один и тот же объект может быть вложен несколько раз.

Например, допустим структуру данных семейного дерева:

// ... 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);

Вы можете указать максимальную глубину для определенного свойства. Например, вы можете установить максимальную глубину как 1, чтобы всегда сериализировать только чью-то мать (а не бабушку и т.д.):

1
2
3
4
5
6
7
8
9
10
11
12
// src/Model/Person.php
namespace App\Model;

use Symfony\Component\Serializer\Attribute\MaxDepth;

class Person
{
    #[MaxDepth(1)]
    private ?self $mother;

    // ...
}

Чтобы ограничить глубину сериализации, в контексте необходимо установить ключ AbstractObjectNormalizer::ENABLE_MAX_DEPTH в значение true (или в контексте по умолчанию, определенном в файле framework.yaml):

1
2
3
4
5
6
7
8
9
10
// ...
$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 содержит {"name":"Joe","mother":{"name":"Sophie"}}

Вы также можете сконфигурировать пользовательское вызываемое, которое будет использовано, когда будет достигнута максимальная глубина. Это может быть использовано, например, для возвращения уникального идентификатора следующего вложенного объекта, вместо того чтобы опускать свойство:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);

// все параметры обратного вызова являются необязательными (вы можете опустить те, которые не используете)
$maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, ?string $format = null, array $context = []): ?string {
    // вернуть только имя следующего человека в дереве
    return $innerObject instanceof Person ? $innerObject->getName() : null;
};

$jsonContent = $serializer->serialize($child, null, [
    AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
    AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler,
]);
// $jsonContent содержит {"name":"Joe","mother":{"name":"Sophie","mother":"Jane"}}

Использование обратных вызовов для сериализации свойств с помощью экземпляров объектов

Во время сериализации вы можете задать обратный вызов для форматирования определенного свойства объекта. Это можно использовать вместо определения контекста для группы :

1
2
3
4
5
6
7
8
9
10
11
12
13
$person = new Person('cordoval', 34);
$person->setCreatedAt(new \DateTime('now'));

$context = [
    AbstractNormalizer::CALLBACKS => [
        // все параметры обратного вызова являются необязательными (вы можете опустить те, которые не используете)
        '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', $context);
// $jsonContent содержит {"name":"cordoval","age":34,"createdAt":"2014-03-22T09:43:12-0500"}

Продвинутая десериализация

Требовать все свойства

По умолчанию, Serializer добавит null к свойствам, которые можно обнулить, если для них не указаны параметры. Вы можете изменить это поведение, установив опцию контекста AbstractNormalizer::REQUIRE_ALL_PROPERTIES в значение true:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,
]);
// вызывает Symfony\Component\Serializer\Exception\MissingConstructorArgumentException

Сбор ошибок типа во время денормализации

При денормализации полезной нагрузки к объекту с типизированными свойствами, вы получите исключение, если полезная нагрузка содержит свойства, не имеющие такого же типа, что и объект.

Используйте COLLECT_DENORMALIZATION_ERRORS, чтобы собрать все исключения. сразу и чтобы получить объект частично денормализованным:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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));
    }

    // ... вернуть список нарушений пользователю
}

Десериализация в существующий объект

Сериализатор также можно использовать для обновления существующего объекта. Вы можете сделать это с помощью опции контекста сериализатора object_to_populate:

1
2
3
4
5
6
7
8
9
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;

// ...
$person = new Person('Jane Doe', 59);

$serializer->deserialize($jsonData, Person::class, 'json', [
    AbstractNormalizer::OBJECT_TO_POPULATE => $person,
]);
// вместо возвращения нового объекта, обновляется $person

Note

Опция AbstractNormalizer::OBJECT_TO_POPULATE используется только для объекта верхнего уровня. Если этот объект является корнем древовидной структуры, все дочерние элементы, существующие в нормализованных данных, будут преобразованы с новыми экземплярами.

Когда опция контекста AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE установлена ​​в значение true, имеющиеся дочерние объекты корня OBJECT_TO_POPULATE обновляются из нормализованных данных, вместо того, чтобы денормализатор преобразовал их. Это работает только для одиночных дочерних объектов, а не для массивов объектов. Они все равно будут заменены, если присутствуют в нормализованных данных.

Десериализация интерфейсов и абстрактных классов

При работе с ассоциированными объектами свойство иногда ссылается на интерфейс или абстрактный класс. При десериализации этих свойств Serializer должен знать, какой конкретный класс будет инициализирован. Это делается с помощью дискриминаторного отображения классов.

Представьте, что есть интерфейс InvoiceItemInterface, который реализован объектами Product и Shipping. При сериализации объекта, сериализатор добавит дополнительный "атрибут дискриминатора". Этот атрибут содержит или product, или shipping. Карта класса дискриминатора сопоставляет эти имена типов с реальными именами классов PHP при десериализации:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace App\Model;

use Symfony\Component\Serializer\Attribute\DiscriminatorMap;

#[DiscriminatorMap(
    typeProperty: 'type',
    mapping: [
        'product' => Product::class,
        'shipping' => Shipping::class,
    ]
)]
interface InvoiceItemInterface
{
    // ...
}

Со сконфигурированной картой дискриминатора, сериализатор теперь может выбрать правильный класс для свойств, типизированных как InvoiceItemInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 содержит {"type":"product",...}

$invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json');
// $invoiceLine содержит новый InvoiceLine(new Product(...))

Частичная десериализация ввода (распаковка)

Сериализатор всегда десериализирует всю строку ввода в значение PHP. При подключении
к посторонним API вам часто нужна только определенная часть возвращаемого ответа.

Чтобы избежать десериализации всего ответа, вы можете использовать UnwrappingDenormalizer и "распаковать" данные ввода:

$jsonData = '{"result":"success","data":{"person":{"name": "Jane Doe","age":57}}}'; $data = $serialiser->deserialize($jsonData, Object::class, [ UnwrappingDenormalizer::UNWRAP_PATH => '[data][person]', ]); // $data - Person(name: 'Jane Doe', age: 57)

unwrap_path - это путь свойства компонента PropertyAccess, примененный к денормализованному массиву.

Работа с аргументами конструктора

Если в конструкторе класса определены аргументы, как это обычно бывает с Value Objects, сериализатор будет сопоставлять имена параметров с десериализированными атрибутами. Если некоторых параметров не хватает, будет вызвано MissingConstructorArgumentsException.

В этих случаях используйте опцию контекста default_constructor_arguments, чтобы определить значения по умолчанию для отсутствующих параметров:

use AppModelPerson; use SymfonyComponentSerializerNormalizerAbstractNormalizer; // ...

$jsonData = '{"age":39,"name":"Jane Doe"}'; $person = $serializer->deserialize($jsonData, Person::class, 'json', [ AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [ Person::class => ['sportsperson' => true], ], ]); // $person - это Person(name: 'Jane Doe', age: 39, sportsperson: true);

Рекурсивная денормализация и безопасность типов

Если доступен ExtractorTypeExtractor, нормализатор также проверит, соответствуют ли данные для денормализации типу свойства (даже для примитивных типов). Например, если предоставлено string, а тип свойства - int, то будет вызвано UnexpectedValueException. Принудительное применение типов свойств можно отключить с помощью установки опции контекста сериализатора ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT в значение true.

Работа с булевыми занчениями

7.1

Опция контекстп AbstractNormalizer::FILTER_BOOL была представлена в Symfony 7.1.

PHP рассматривает много разных значений как истинные или ложные. Например, строки true, 1 и yes считаются истинными, тогда как false, 0 и no считаются ложными.

При десериализации компонент Serializer может позаботиться об этом автоматически. Это можно сделать с помощью опции контекста AbstractNormalizer::FILTER_BOOL:

1
2
3
4
5
6
7
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
// ...

$person = $serializer->denormalize(['sportsperson' => 'yes'], Person::class, context: [
    AbstractNormalizer::FILTER_BOOL => true
]);
// $person содержит экземпляр Person с sportsperson, установленным как true

Этот контекст заставляет процесс десериализации вести себя как функция filter_var с флажком FILTER_VALIDATE_BOOL.

Конфигурация кэша метаданных

Метаданные для сериализатора автоматически кэшируются для повышения производительности приложения. По умолчанию сериализатор использует пул кэша cache.system, который конфигурируется с помощью опции cache.system .