Компонент Serializer

Дата обновления перевода: 2024-07-10

Компонент Serializer

Компонент Serializer предназначается для того, чтобы превращать объекты в определённый формат (XML, JSON, YAML, ...) и наоборот.

Для того, чтобы сделать это, компонент Serializer следует такой простой схеме.

Как вы можете увидеть на изображении выше, массив используются в качестве посредника между объектами и сериализованным содержанием. Таким образом, кодировщики (Encoders) будут работать только с превращением конкретных форматов в массивы и наоборот. Таким же образом, нормализаторы (Normalizers) будут работать с превращением определённых объектов в массивы и наоборот.

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

Установка

1
$ composer require symfony/serializer

Note

Если вы устанавливаете этот компонент вне приложения Symfony, вам нужно подключить файл vendor/autoload.php в вашем коде для включения механизма автозагрузки классов, предоставляемых Composer. Детальнее читайте в этой статье.

Для использования ObjectNormalizer, должен быть также установлен компонент PropertyAccess.

Применение

See also

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

Использовать компонент Serializer очень просто. Вам просто нужно установить Serializer, указывая, какие кодировщики и нормализатор будут доступны:

1
2
3
4
5
6
7
8
9
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);

Предпочитаемый нормализатор - ObjectNormalizer, но другие нормализаторы также доступны. Все примеры, показанные ниже, используют ObjectNormalizer.

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

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

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
49
50
51
52
namespace App\Model;

class Person
{
    private int $age;
    private string $name;
    private bool $sportsperson;
    private ?\DateTimeInterface $createdAt;

    // Геттеры
    public function getAge(): int
    {
        return $this->age;
    }

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

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }

    // Иссеры
    public function isSportsperson(): bool
    {
        return $this->sportsperson;
    }

    // Сеттеры
    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;
    }
}

Теперь, если вы хотите сериализовать этот объект в JSON, вам просто нужно использовать сервис Serializer, созданный ранее:

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

$person = new Person();
$person->setName('foo');
$person->setAge(99);
$person->setSportsperson(false);

$jsonContent = $serializer->serialize($person, 'json');

// $jsonContent содержит {"name":"foo","age":99,"sportsperson":false,"createdAt":null}

echo $jsonContent; // или вернуть его в Ответе

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

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

Теперь вы узнаете, как делать с точностью до наоборот. В этот раз, информация класса Person будет зашифрована в формате XML:

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

$data = <<<EOF
<person>
    <name>foo</name>
    <age>99</age>
    <sportsperson>false</sportsperson>
</person>
EOF;

$person = $serializer->deserialize($data, Person::class, 'xml');

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use App\Model\Person;

$data = <<<EOF
<person>
    <name>foo</name>
    <age>99</age>
    <city>Paris</city>
</person>
EOF;

// $loader является любым из валидных загрузчиков, описанных позднее в этой статье
$classMetadataFactory = new ClassMetadataFactory($loader);
$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer([$normalizer]);

// это вызовет Symfony\Component\Serializer\Exception\ExtraAttributesException,
// так как "city" не является атрибутом класса Person
$person = $serializer->deserialize($data, Person::class, 'xml', [
    AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);

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

Serializer также может быть использован для обновления существующего объекта:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ...
$person = new Person();
$person->setName('bar');
$person->setAge(99);
$person->setSportsperson(true);

$data = <<<EOF
<person>
    <name>foo</name>
    <age>69</age>
</person>
EOF;

$serializer->deserialize($data, Person::class, 'xml', [AbstractNormalizer::OBJECT_TO_POPULATE => $person]);
// $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true)

Это распространённая необходимость, при работе с ORM.

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

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

Контекст

Многие функции Serializer можно сконфигурировать, используя контекст.

Группы атрибутов

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

Предположите, что у вас есть следующий простой PHP объект:

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

Определение сериализатора может быть указано используя аннотация, XML или YAML. ClassMetadataFactory, который будет использован нормализатором должен знать, какой формат использовать.

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

  • Атрибуты в PHP-файлах:

    1
    2
    3
    4
    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
    use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
    
    $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
  • YAML-файлы:

    1
    2
    3
    4
    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-файлы:

    1
    2
    3
    4
    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
    use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
    
    $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml'));

Далее, создайте ваше определение групп:

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

    // ...
}

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

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
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',
    null,
    ['groups' => ['group1', 'group3']]
);
// $obj2 = MyObj(foo: 'foo', bar: 'bar')

// Чтобы получить все группы, специальное значение `*` в `groups`
$obj3 = $serializer->denormalize(
    ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'],
    'MyObj',
    null,
    ['groups' => ['*']]
);
// $obj2 = MyObj(foo: 'foo', anotherProperty: 'anotherProperty', bar: 'bar')

Выбор определённых атрибутов

Также возможно сериализовать только набор определённых атрибутов:

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
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 string $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']];

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

Что касается групп, атрибуты могут быть выбраны как во время процесса сериализации, так и десериализации.

Игнорирование атрибутов

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

Вариант 1: Используя атрибут #[Ignore]

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

use Symfony\Component\Serializer\Annotation\Ignore;

class MyClass
{
    public string $foo;

    #[Ignore]
    public string $bar;
}

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

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

Вариант 2: Используя контекст

Передайте массив с именами атрибутов, которые нужно проигнорировать, используя ключ AbstractNormalizer::IGNORED_ATTRIBUTES в context метода сериализатора:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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']]); // Вывод: {"name":"foo"}

Конвертация имён свойств при сериализации и десериализации

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

Компонент Serializer предоставляет удобный способ перевести или отобразить имена PHP-полей в сериализованные имена: Систему конвертации имен.

При условии, что у вас есть следующий объект:

1
2
3
4
5
class Company
{
    public string $name;
    public string $address;
}

А в сериализованной форме все атрибуты должны иметь префикс org_, как показано далее:

1
{"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"}

Пользовательский конвертер имен может работать с такими случаями:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
    {
        // удаляет префикс 'org_'
        return 'org_' === substr($propertyName, 0, 4) ? substr($propertyName, 4) : $propertyName;
    }
}

Пользовательский конвертер имен может быть использован путем его передачи в качестве второго параметра любого класса, расширяющего AbstractNormalizer, включительно с GetSetMethodNormalizer и PropertyNormalizer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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');
// Те же данные, что и $company

Note

Вы также можете реализовать AdvancedNameConverterInterface, чтобы получить доступ к текущему имени, формату и контексту класса.

Из CamelCase в snake_case

Во многих форматах распространено использование нижних подчёркиваний для разделения слов (также известно, как snake_case). Однако, в приложениях Symfony часто используется CamelCase для именования свойств (несмотря на то, что стандарт PSR-1 не рекомендует никакой определённый стиль для имён свойств).

Symfony предоставляет встроенный преобразователь имён, созданный для преобразований между стилями snake_case и CamelCased во время процессов сериализации и десериализации:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use Symfony\Component\Serializer\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 с firstName: 'Anne'

Конфигурация конверсии имён с использованием метаданных

Если этот компонент используется внутри приложения Symfony и фабрика класса метаданных включена, как объясняется в Attributes Groups section , все уже настроено и вам нужно только предоставить конфигурацию. В других случаях:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...
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 AnnotationLoader(new AnnotationReader()));

$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);

$serializer = new Serializer(
    [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)],
    ['json' => new JsonEncoder()]
);

Теперь сконфигурируйте отображение вашей конверсии имен. Рассмотрите приложение, которое определяет сущность Person со свойством firstName:

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

use Symfony\Component\Serializer\Annotation\SerializedName;

class Person
{
    public function __construct(
        #[SerializedName('customer_name')]
        private string $firstName,
    ) {
    }

    // ...
}

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

1
2
$serialized = $serializer->serialize(new Person('Kévin'), 'json');
// {"customer_name": "Kévin"}

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

Во время сериализации

Если вы используете методы иссеров (методы, с префиксом is, вроде Acme\Person::isSportsperson()), компонент Serializer автоматически определит его и использует для сериализации связанных атрибутов.

ObjectNormalizer также заботится о методах, начинающихся на has, add и remove.

Во время десериализации

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

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

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

$normalizer = new ObjectNormalizer();
$serializer = new Serializer([$normalizer]);

$data = $serializer->denormalize(['sportsperson' => 'yes'], Person::class, context: [AbstractNormalizer::FILTER_BOOL => true]);

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

7.1

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

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

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

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
use App\Model\Person;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;

$encoder = new JsonEncoder();

// все параметры обратного вызова опциональны (вы можете опустить те, которые вы не используете)
$dateCallback = function (object $innerObject, object $outerObject, string $attributeName, string $format = null, array $context = []): string {
    return $innerObject instanceof \DateTime ? $innerObject->format(\DateTime::ISO8601) : '';
};

$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');
// Вывод: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"}

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

Нормализаторы превращают объект в массив и наоборот. Они реализуют NormalizableInterface для нормализации (объекта в массив) и DenormalizableInterface для денормализации (массива в объект).

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

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

$normalizers = [new ObjectNormalizer()];
$serializer = new Serializer($normalizers, []);

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

Компонент Serializer предоставляет несколько встроенных нормализаторов:

ObjectNormalizer

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

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

ObjectNormalizer - это самый мощный нормализатор. Он конфигурируется по умолчанию в приложениях Symfony с включенным компонентом Serializer.

GetSetMethodNormalizer

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

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

PropertyNormalizer

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

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

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

JsonSerializableNormalizer

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

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

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

В отличие от json_encode циклические ссылки могут быть обработаны.

DateTimeNormalizer

Этот нормализатор конвертирует объекты DateTimeInterface (например, DateTime и DateTimeImmutable) в строки. По умолчанию, он использует формат RFC3339. Для того, чтобы преобразовать объекты в целые или плавающие числа, установите опцию контекста сериализатора
DateTimeNormalizer::CAST_KEY в значение int или float.

7.1

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

DateTimeZoneNormalizer
Этот нормализатор конвертирует объекты DateTimeZone в строки, которые представляют название временной зоны в соответствии со Списком PHP временных зон.
DataUriNormalizer
Этот нормализатор конвертирует объекты SplFileInfo в данные URI строки (data:...) так, что файлы могут быть встроены в сериализованные данные.
DateIntervalNormalizer
Этот нормализатор конвертирует объекты DateInterval в строки. По умолчанию, он использует формат P%yY%mM%dDT%hH%iM%sS.
BackedEnumNormalizer

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

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

FormErrorNormalizer

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

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

ConstraintViolationListNormalizer
Этот нормализатор конвертирует объекты, реализующие ConstraintViolationListInterface в список ошибок в соответствии со стандартом RFC 7807.
ProblemNormalizer
Нормализирует ошибки в соотстветствии со спецификацией проблем API RFC 7807.

CustomNormalizer

Нормализует PHP-объект используя объект, реализующий NormalizableInterface.

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 в Uuid или Ulid. Формат не имеет значения.

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

Note

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

Некоторые нормализаторы включаются по умолчанию при использовании компонент Serializer в приложении Symfony, дополнительные можно включить, добавив к ним тег serializer.normalizer .

Вот пример того, как включить встроенный GetSetMethodNormalizer, более быструю альтернативу ObjectNormalizer:

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

    get_set_method_normalizer:
        class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
        tags: [serializer.normalizer]

Кодировщики

Кодировщики превращают массивы в форматы и наоборот. Они реализуют EncoderInterface для кодирования (массив в формат) и DecoderInterface для декодирования (формат в массив).

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

1
2
3
4
5
6
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;

$encoders = array(new XmlEncoder(), new JsonEncoder());
$serializer = new Serializer(array(), $encoders);

Встроенные кодировщики

Компонент Serializer предоставляет несколько встроенных кодировщиков:

JsonEncoder
Этот класс зашифровывает и расшифровывает данные в JSON.
XmlEncoder
Этот класс зашифровывает и расшифровывает данные в XML.
YamlEncoder
Этот кодировщик зашифровывает и расшифровывает данные в YAML. Кодировщик требует компонент Yaml.
CsvEncoder
Этот кодировщик зашифровывает и расшифровывает данные в CSV.

Note

Вы такоже можете создать собственый кодировщик, чтобы использовать другую структуру. Прочтите больше в Как создать ваш пользовательский кодировщик.

Все эти кодировщики включены по умолчанию при использовании стандартной версии Symfony с включенным сериализатором.

JsonEncoder

JsonEncoder шифрует и дешифрует из JSON-строк, основываясь на PHP-функциях json_encode и json_decode. Он может быть полезен для модификации того, как это функции работают в определенных экземплярах, предоставляя опции вроде JSON_PRESERVE_ZERO_FRACTION. Вы можете использовать контекст сериализации, чтобы передать эти опции, используя ключ json_encode_options или json_decode_options, соответственно:

1
$this->serializer->serialize($data, 'json', ['json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION]);

Вот доступные опции:

????? ???????? ?? ?????????
json_decode_associative ???? ??????????? true, ?????????? ????????? ? ???? ???????, ????? - ?????????? ????????? ???????? stdClass. false
json_decode_detailed_errors ???? ??????????? true, ??????????, ????????? ??? ??????? JSON, ????? ??????????. ??????? ?????? seld/jsonlint. false
json_encode_options $flags, ?????????? ??????? json_decode. 0
json_decode_options $flags ?????????? ??????? json_encode. \JSON_PRESERVE_ZERO_FRACTION
json_decode_recursion_depth ????????????? ???????????? ??????? ????????. 512

CsvEncoder

CsvEncoder шифрует и расшифровывает в и из CSV.

Опции контекста CsvEncoder

Метод encode() определяет третий необязательный параметр, под названием context, которые определяет опции конфигурации для ассоциативного массива CsvEncoder:

1
$csvEncoder->encode($array, 'csv', $context);

Вот доступные опции:

????? ???????? ???????? ?? ?????????
csv_delimiter ????????????? ??????????? ????, ?????????? ???????? (?????? ???? ??????) ,
csv_enclosure ????????????? ???????? ???? (?????? ???? ??????) "
csv_end_of_line ???????????? ??????(?), ???????????? ??? ??????? ????????? ?????? ??????? ? CSV-????? \n
csv_escape_char ????????????? ?????? ???????? (????. ???? ??????) ?????? ??????
csv_key_separator ????????????? ??????????? ??? ?????? ??????? ?? ????? ??? ????????? .
csv_headers ????????????? ??????? ???????? ?????????? ? ?????? ??????: ???? $data = ['c' => 3, 'a' => 1, 'b' => 2] ? $options = ['csv_headers' => ['a', 'b', 'c']] ?? serialize($data, 'csv', $options) ?????????? a,b,c\n1,2,3 [], ????????? ?? ?????? ?????? ?????
csv_escape_formulas ???????? ?????, ?????????? ???????, ???????? ? ?????? ??????\t false
as_collection ?????? ?????????? ????????? ? ???? ?????????, ???? ???? ??????????? ?????? ???? ??????? true
no_headers ????????? ????????? ? ????????????? CSV false
output_utf8_bom ??????? ??????????? UTF-8 BOM ?????? ? ?????????????? ??????? false

XmlEncoder

Этот кодировщик преобразует массивы в XML и наоборот.

Например, возьмём объект нормализированный следующим образом:

1
['foo' => [1, 2], 'bar' => true];

XmlEncoder зашифрует этот объект следующим образом:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" ?>
<response>
    <foo>1</foo>
    <foo>2</foo>
    <bar>1</bar>
</response>

Специальный ключ # может быть использован для определения данных узла:

1
2
3
4
5
6
7
8
9
['foo' => ['@bar' => 'value', '#' => 'baz']];

// зашифрован следующим образом:
// <?xml version="1.0"?>
// <response>
//     <foo bar="value">
//        baz
//     </foo>
// </response>

Более того, ключи, начинающиеся с @, будут счтаться атрибутами, а ключ #comment может быть использован для зашифровки XML-комментариев:

1
2
3
4
5
6
7
8
9
10
11
$encoder = new XmlEncoder();
$encoder->encode([
    'foo' => ['@bar' => 'value'],
    'qux' => ['#comment' => 'A comment'],
], 'xml');
// вернет:
// <?xml version="1.0"?>
// <response>
//     <foo bar="value"/>
//     <qux><!-- A comment --!><qux>
// </response>

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

Note

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

1
2
3
4
5
6
7
8
9
10
11
12
$encoder = new XmlEncoder();
$encoder->encode([
    '@attribute1' => 'foo',
    '@attribute2' => 'bar',
    '#' => ['foo' => ['@bar' => 'value', '#' => 'baz']]
], 'xml');

// will return:
// <?xml version="1.0"?>
// <response attribute1="foo" attribute2="bar">
// <foo bar="value">baz</foo>
// </response>

Tip

XML-комментарии игнорируются по умолчанию при дешифровке содержания, но это поведение можно изменить с помощью необязательного ключа XmlEncoder::DECODER_IGNORED_NODE_TYPES.

Данные с ключами #comment шифруются в XML-комментарии по умолчанию. Это можно изменить, добавив опцию \XML_COMMENT_NODE к ключу XmlEncoder::ENCODER_IGNORED_NODE_TYPES в $defaultContext конструктора XmlEncoder или напрямую в аргумент $context метода encode():

1
$xmlEncoder->encode($array, 'xml', [XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_COMMENT_NODE]]);

Опции контекста XmlEncoder

Метод encode() определяет третий необязательный параметр под названием context, который определяет опции конфигурации для ассоциативного массива XmlEncoder :

1
$xmlEncoder->encode($array, 'xml', $context);

Доступны следующие опции:

????? ???????? ???????? ?? ?????????
xml_format_output ???? ?????????? ??? true, ??????????? ??????????????? XML ????????? ?????? ? ????????? false
xml_version ????????????? XML-?????? ???????? 1.1
xml_encoding ????????????? ???????, ??????????????? XML utf-8
xml_standalone ????????? ????????? ??????? ? ??????????????? XML true
xml_type_cast_attributes ????????????? ??????????? ???????? ?????????? ???? ???????? true
xml_root_node_name ????????????? ??? ???? ?????. response
as_collection ?????? ?????????? ????????? ? ???? ?????????, ???? ???? ??????????? ?????? ???? ??????? false
decoder_ignored_node_types ?????? ????? ????? (const DOM XML_*), ??????? ????? ???????????? ??? ?????????? [\XML_PI_NODE, \XML_COMMENT_NODE]
encoder_ignored_node_types ?????? ????? ????? (const DOM XML_*), ??????? ????? ???????????? ??? ??????????? []
load_options XML, ??????????? ????? libxml \LIBXML_NONET | \LIBXML_NOBLANKS
save_options XML, ??????????? ????? ? libxml 0
remove_empty_tags ???? ?????????? ??? true, ??????? ??? ?????? ???? ? ??????????????? XML false

Пример с пользовательским context:

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
use Symfony\Component\Serializer\Encoder\XmlEncoder;

// создать кодировщик с указанными опциями в качестве новых настроек по умолчанию
$xmlEncoder = new XmlEncoder(['xml_format_output' => true]);

$data = [
    'id' => 'IDHNQIItNyQ',
    'date' => '2019-10-24',
];

// зашифровать с контекстом по умолчанию
$xmlEncoder->encode($data, 'xml');
// выводит:
// <?xml version="1.0"?>
// <response>
//   <id>IDHNQIItNyQ</id>
//   <date>2019-10-24</date>
// </response>

// зашифровать с измененным контекстом
$xmlEncoder->encode($data, 'xml', [
    'xml_root_node_name' => 'track',
    'encoder_ignored_node_types' => [
        \XML_PI_NODE, // removes XML declaration (the leading xml tag)
    ],
]);
// выводит:
// <track>
//   <id>IDHNQIItNyQ</id>
//   <date>2019-10-24</date>
// </track>

YamlEncoder

Этот кодировщик требует Компонент Yaml и преобразует в и из Yaml.

Опции контекста YamlEncoder

Метод encode(), как и другой кодировщик, использует context, чтобы установить опции конфигурации для ассоциативного массива YamlEncoder:

1
$yamlEncoder->encode($array, 'yaml', $context);

Вот доступные отзывы:

????? ???????? ???????? ?? ?????????
yaml_inline ???????, ?? ??????? ?? ?????????????? ?? ???????????? YAML 0
yaml_indent ?????? ???????? (???????????? ?????????) 0
yaml_flags ????????? ???-???? Yaml::DUMP_* / PARSE_* ??? ????????? (??)???????? YAML-?????? 0

Конструкторы контекста

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

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

Note

Компонент Serializer предоставляет конструктор контекста для каждого нормализатора и кодировщика .

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

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

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

1
2
3
4
5
6
7
8
$dummy = new class {
    public $foo;
    public $bar = 'notNull';
};

$normalizer = new ObjectNormalizer();
$result = $normalizer->normalize($dummy, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
// ['bar' => 'notNull']

Требование всех свойств

По умолчанию Serializer добавляет null к нулевым свойствам, если параметры для них не указаны. Вы можете изменить это поведение, установив опцию контекста
AbstractNormalizer::REQUIRE_ALL_PROPERTIES в значение 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]); // вызывает SymfonyComponentSerializerExceptionMissingConstructorArgumentException

Пропуск неинициализированных свойств

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

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

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

1
2
3
4
5
6
7
8
$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']

Note

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try {
    $dto = $serializer->deserialize($request->getContent(), MyDto::class, 'json', [
        DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
    ]);
} catch (PartialDenormalizationException $e) {
    $violations = new ConstraintViolationList();
    /** @var NotNormalizableValueException */
    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);
}

Работа с циклическими ссылками

Циклические ссылки распространены при работе с отношениями сущности:

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
49
50
51
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;
    }
}

Чтобы избежать бесконечных циклов, GetSetMethodNormalizer или ObjectNormalizer, вызоваите CircularReferenceException, когда столкнётесь с таким случаем:

1
2
3
4
5
6
7
8
9
10
$member = new Member();
$member->setName('Kévin');

$organization = new Organization();
$organization->setName('Les-Tilleuls.coop');
$organization->setMembers(array($member));

$member->setOrganization($organization);

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

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

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

1
2
3
4
5
6
7
8
9
10
11
$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 может определять и ограничивать глубину сериализации. Это особенно полезно при сериализации больших древ. Представьте следующую структуру данных:

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

Serializer может быть сконфигурирован, чтобы установить максимальную глубину данного свойства. Здесь мы установили его, как 2 для свойства $child:

1
2
3
4
5
6
7
8
9
10
11
namespace Acme;

use Symfony\Component\Serializer\Annotation\MaxDepth;

class MyObj
{
    #[MaxDepth(2)]
    public MyObj $child;

    // ...
}

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

Проверка производится только если ключ AbstractObjectNormalizer::ENABLE_MAX_DEPTH контекста сериализатора установлен, как true. В следующем примере, третий уровень не сериализуется, так как он глубже, чем максимальная сконфигурированная глубина (2):

1
2
3
4
5
6
7
8
9
10
11
12
$result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]);
/*
$result = [
    'foo' => 'level1',
    'child' => [
        'foo' => 'level2',
        'child' => [
            'child' => null,
        ],
    ],
];
*/

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

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
49
50
51
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\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 AnnotationLoader(new AnnotationReader()));

// все параметры обратных вызовов не обязательны (вы можете пропустить те, которые не используете)
$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',
    ],
];
*/

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 содержит [{"name":"foo","age":99,"sportsperson":false},{"name":"bar","age":33,"sportsperson":true}]

Если вы хотите десериализовать такую структуру, то вам нужно добавить ArrayDenormalizer к набору нормализаторов. Добавив [] к типу параметра метода deserialize(), вы обозначите, что вы ожидаете массив вместо одного объекта:

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

Обработка аргументов конструктора

Если у конструктора класса есть аргументы, что обычно случается в Value Objects, сериализатор не сможет создать объект, если пропущены какие-то аргументы. В данных случаях используйте контекстную опцию default_constructor_arguments:

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
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($classMetadataFactory);
$serializer = new Serializer([$normalizer]);

$data = $serializer->denormalize(
    ['foo' => 'Hello'],
    'MyObj',
    null,
    [AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [
        'MyObj' => ['foo' => '', 'bar' => ''],
    ]]
);
// $data = new MyObj('Hello', '');

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

Компонент Serializer может использовать PropertyInfo Component, чтобы денормализовать сложные типы (объекты). Тип свойства класса будет предположен, используя предоставленный извлекатель и использован для рекурсивной денормализации внутренних данных.

При использовании стандартной версии Symfony, все нормализаторы автоматически конфигурируются, чтобы использовать зарегистрированные извлекатели. При использовании копомнента самостоятельно, реализация PropertyTypeExtractorInterface (обычно экземпляр PropertyInfoExtractor) должна быть передана в качестве 4го параметра ObjectNormalizer:

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
49
50
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'

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

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

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

Дискриминатор - это поле (в сериализованной строке), используемое для дифференциации между возможными объектами. На практике, при использвании компонента Serializer, передайте реализацию ClassDiscriminatorResolverInterface ObjectNormalizer.

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

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

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

$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);

$serializer = new Serializer(
    [new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator)],
    ['json' => new JsonEncoder()]
);

Теперь сконфигурируйте ваше отображение класса дискриминатора. Рассмотрите приложение, которое определяет абстрактный класс CodeRepository, расширенный классами GitHubCodeRepository и BitBucketCodeRepository:

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

Note

Значения опции массива mapping должны быть строками. В противном случае они будут приведены к строкам автоматически.

После конфигурации, cериализатор использует отображение для выбора правильного класса:

1
2
3
4
5
$serialized = $serializer->serialize(new GitHubCodeRepository(), 'json');
// {"type": "github"}

$repository = $serializer->deserialize($serialized, CodeRepository::class, 'json');
// instanceof GitHubCodeRepository

Узнайте больше

See also

Нормализаторы для компонента Symfony Сериализатор, поддерживающие популярные API-форматы (JSON-LD, GraphQL, OpenAPI, HAL, JSON:API) доступны как часть проекта API Platform.

See also

Популярной альтернативой компоненту Serializer Symfony является сторонняя библиотека - JMS сериализатор (версии до v1.12.0 были выпущены под лицензией Apache, поэтому несовместимэ с проектами GPLv2).