Компонент Serializer

Дата оновлення перекладу 2024-05-03

Компонент Serializer

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

Для того, щоб зробити це, компонент Serializer дотримується такої простої схеми.

При (де)серіалізації об'єктів Serializer використовує масив як посередника між об'єктами та серіалізованим змістом. Кодувальники будуть працювати лище з перетворенням конкретних форматів на масиви і навпаки. Таким же саме чином, нормалізатори будуть працювати з перетворенням певних об'єктів на масиви і навпаки.

Серіалізація - це складна тема. Цей компонент може не охопити всі ваши випадки застосування, але може бути корисним для розробки інструментів для серіалізації та десеріалізації ваших об'єктів.

Установка

1
$ composer require symfony/serializer

Note

Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити файл vendor/autoload.phpу вашому коді для включення механізму автозавантаження класів, наданих Composer. Детальніше можна прочитати у цій статті.

Для використання ObjectNormalizer, має бути також встановлений компонент PropertyAccess.

Застосування

See also

Ця стаття пояснює як використовувати функції 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, вам просто необхідно використати сервіс Серіалізатор, створений раніше:

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 contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null}

echo $jsonContent; // or return it in a Response

Перший параметр 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. Кодувальник, який використовується для перетворення цієї інформації в масив

За замовчуванням, додаткові атрибути, які не пов'язуються з денормалізованим об'єктом, будуть проігноровані компонентом Serializer. Якщо ви віддаєте перевагу отриманню виключення, коли це стається, встановіть опцію контексту 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,
]);

Десеріалізація в існуючому об'єкті

Серіалізатор також може бути використаний для оновлення існуючого об'єкту:

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, існуючі дочки кореню 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
    5
    use Doctrine\Common\Annotations\AnnotationReader;
    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
    use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
    
    $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
  • 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)
    {
        return 'org_'.$propertyName;
    }

    public function denormalize(string $propertyName)
    {
        // видаляє префікс '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 і фабрика класу метаданих увімкнена, як пояснюється в розділі Груп атрибутів , все вже налаштовано і вам необхідно лише надати конфігурацію. В інших випадках:

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, наприклад App\Model\Person::isSportsperson()), компонент Serializer автоматично визначить і використає його для серіалізації пов'язаних атрибутів.

Компонент ObjectNormalizer також піклується про методи, що починаються з has, get та can.

Під час десеріалізації

PHP розглядає багато різних значень як істинні або хибні. Наприклад, рядки рядки 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 щоб зчитувати та робити запити в об'єкті. Це означає, що він має доступ до властивості напряму через геттери, сеттери, хассери, іссери, аддери та ремувери. Він підтримує виклик конструктора під час процесу денормалізації.

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

ObjectNormalizer - це найпотужніший нормалізатор. Він конфігурується за замовчуванням в додатках Symfony з увімкненим компонентом Серіалізатор.

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 на Серіалізатор 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');

// поверне:
// <?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);

Доступні наступні опції:

Приклад з користувацьким 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

За замовчуванням, Серіалізатор буде зберігати властивості, що містять значення null. Ви можете змінити цю поведінку, встановивши опцію контексту AbstractObjectNormalizer::SKIP_NULL_VALUES як true:

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']

Вимагати всі властивості

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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]);
// викликає Symfony\Component\Serializer\Exception\MissingConstructorArgumentException

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

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

Щоб уникнути виклику помилки серіалізатором при серіалізації або нормалізації обʼєкта з неініціалізованими властивостями, за замовчуванням, нормалізатор обʼєкта ловить ці помилки та ігнорує такі властивості.

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

1
2
3
4
5
6
7
8
class Dummy {
    public string $foo = 'initialized';
    public string $bar; // uninitialized
}

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

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 $exception */
    foreach ($e->getErrors() as $exception) {
        $message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType());
        $parameters = [];
        if ($exception->canUseMessageForUser()) {
            $parameters['hint'] = $exception->getMessage();
        }
        $violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null));
    }

    return $this->json($violations, 400);
}

Робота з циклічними посиланнями

Циклічні посилання розповсюджені при роботі з відносинами сутності:

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;

Серіалізатор може бути сконфігурований, щоб встановити максимальну глибину даної властивості. Тут ми встановили його, як 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');

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

Якщо у конструктора класу є аргументи, що зазвичай буває в Об'єктах цінності, серіалізатор не зможе створити об'єкт, якщо пропущені якісь аргументи. В даних випадках використовуйте контекстну опцію 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, щоб денормалізувати складні типи (об'єкти). Тип властивості класу буде припущено, використовуючи наданий добувач та використаний для рекурсивної денормалізації внутрішніх даних.

При використанні стандартної версії Symfony, всі нормалізатори автоматично конфігуруються, щоб використовувати зареєстрованих добувачів. При використанні компоненту самостійно, реалізація PropertyTypeExtractorInterface (зазвичай екземпляр PropertyInfoExtractor) має бути передана в якості четвертого параметру 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 Acme;

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.

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

При роботі з об'єктами, які достатньо схожі, або мають спільні властивості, ви можете використовувати інтерфейси або абстрактні класи. Компонент Серіалізатор дозволяє вам серіалізувати та десеріалізувати ці об'єкти, використовуючи "відображення класу дискримінатору"

Дискримінатор - це поле (в серіалізованому рядку), що використовується для диференціації між можливими об'єктами. На практиці, при використанні компоненту Серіалізатор, передайте реалізацію 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
{
    // ...
}

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

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

$repository = $serializer->deserialize($serialized, CodeRepository::class, 'json');
// екземпляр GitHubCodeRepository

Дізнайтеся більше

See also

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

See also

Популярною альтернативою компоненту Серіалізатор Symfony є стороння бібліотека - JMS серіалізатор (версії до v1.12.0 було випущено під ліцензією Apache, тому несумісні з проектами GPLv2).