Компонент Serializer

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

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

../_images/serializer_workflow.png

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

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

Установка

1
$ composer require symfony/serializer

Alternatively, you can clone the https://github.com/symfony/serializer repository.

Note

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

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

Эта статья объясняет как использовать функции Serializer как независимого компонента в любом приложении PHP. Прочитайте статью How to Use the Serializer для понимания как использовать его в приложениях Symfony.

Использование

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

1
2
3
4
5
6
7
8
9
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$encoders = array(new XmlEncoder(), new JsonEncoder());
$normalizers = array(new ObjectNormalizer());

$serializer = new Serializer($normalizers, $encoders);

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

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

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

 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
namespace App\Model;

class Person
{
    private $age;
    private $name;
    private $sportsperson;
    private $createdAt;

    // Геттеры
    public function getName()
    {
        return $this->name;
    }

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

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

    // Сеттеры
    public function setName($name)
    {
        $this->name = $name;
    }

    public function setAge($age)
    {
        $this->age = $age;
    }

    public function setSportsperson($sportsperson)
    {
        $this->sportsperson = $sportsperson;
    }

    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;
    }
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$person = new App\Model\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. Кодировщик, используемый для преобразования этой информации в массив

По умолчанию, дополнительные атрибуты, которые не связываются с денормализованным объектом, будут проигнорированы компонентом Сереализатор. Установите ключ allow_extra_attributes контекста десериализации, как false, чтобы позволить сериализатору вызывать исключение, когда передаются дополнительные атрибуты:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$data = <<<EOF
<person>
    <name>foo</name>
    <age>99</age>
    <city>Paris</city>
</person>
EOF;

// это вызовет Symfony\Component\Serializer\Exception\ExtraAttributesException
// так как "город" не являтся атрибутом класса Особа
$person = $serializer->deserialize($data, 'App\Model\Person', 'xml', array(
    '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', array('object_to_populate' => $person));
// $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true)

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

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

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

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

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

class MyObj
{
    public $foo;

    private $bar;

    public function getBar()
    {
        return $this->bar;
    }

    public function setBar($bar)
    {
        return $this->bar = $bar;
    }
}

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

Инициализируйте ClassMetadataFactory, как показано далее:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
// Для аннотаций
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
// Для XML
// используйте Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
// Для YAML
// используйте Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
// Для XML
// $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml'));
// Для YAML
// $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml'));

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

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    namespace App\Model;
    
    use Symfony\Component\Serializer\Annotation\Groups;
    
    class MyObj
    {
        /**
         * @Groups({"group1", "group2"})
         */
        public $foo;
    
        /**
         * @Groups({"group3"})
         */
        public function getBar() // is* methods are also supported
        {
            return $this->bar;
        }
    
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    App\Model\MyObj:
        attributes:
            foo:
                groups: ['group1', 'group2']
            bar:
                groups: ['group3']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <?xml version="1.0" ?>
    <serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
            http://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
    >
        <class name="App\Model\MyObj">
            <attribute name="foo">
                <group>group1</group>
                <group>group2</group>
            </attribute>
    
            <attribute name="bar">
                <group>group3</group>
            </attribute>
        </class>
    </serializer>
    

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$obj = new MyObj();
$obj->foo = 'foo';
$obj->setBar('bar');

$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer(array($normalizer));

$data = $serializer->normalize($obj, null, array('groups' => array('group1')));
// $data = array('foo' => 'foo');

$obj2 = $serializer->denormalize(
    array('foo' => 'foo', 'bar' => 'bar'),
    'MyObj',
    null,
    array('groups' => array('group1', 'group3'))
);
// $obj2 = MyObj(foo: 'foo', bar: 'bar')

Note

In order to use the annotation loader, you should have installed the doctrine/annotations and doctrine/cache packages with Composer.

Tip

Annotation classes aren't loaded automatically, so you must load them using a class loader like this:

1
2
3
4
5
6
7
8
9
use Composer\Autoload\ClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;

/** @var ClassLoader $loader */
$loader = require __DIR__.'/../vendor/autoload.php';

AnnotationRegistry::registerLoader([$loader, 'loadClass']);

return $loader;

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

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

 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 Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

class User
{
    public $familyName;
    public $givenName;
    public $company;
}

class Company
{
    public $name;
    public $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(array(new ObjectNormalizer()));

$data = $serializer->normalize($user, null, array('attributes' => array('familyName', 'company' => ['name'])));
// $data = array('familyName' => 'Dunglas', 'company' => array('name' => 'Les-Tilleuls.coop'));

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

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

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

Note

Использование группы атрибутов вместо метода setIgnoredAttributes(), считантся хорошей практикой.

Как вариант, существует способ игнорирования атрибутов из исходного объекта. Чтобы удалить эти атрибуты, используйте метод setIgnoredAttributes() в определении нормализатора:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$normalizer = new ObjectNormalizer();
$normalizer->setIgnoredAttributes(array('age'));
$encoder = new JsonEncoder();

$serializer = new Serializer(array($normalizer), array($encoder));
$serializer->serialize($person, 'json'); // Output: {"name":"foo","sportsperson":false}

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

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

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

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

1
2
3
4
5
class Company
{
    public $name;
    public $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($propertyName)
    {
        return 'org_'.$propertyName;
    }

    public function denormalize($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(array($normalizer), array(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

Из 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
25
26
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter());

class Person
{
    private $firstName;

    public function __construct($firstName)
    {
        $this->firstName = $firstName;
    }

    public function getFirstName()
    {
        return $this->firstName;
    }
}

$kevin = new Person('Kévin');
$normalizer->normalize($kevin);
// ['first_name' => 'Kévin'];

$anne = $normalizer->denormalize(array('first_name' => 'Anne'), 'Person');
// Объект Person с firstName: 'Anne'

Сериализация булевых атрибутов

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

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

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

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

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

$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();

$callback = function ($dateTime) {
    return $dateTime instanceof \DateTime
        ? $dateTime->format(\DateTime::ISO8601)
        : '';
};

$normalizer->setCallbacks(array('createdAt' => $callback));

$serializer = new Serializer(array($normalizer), array($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"}

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

Существует несколько доступных типов сериализаторов:

ObjectNormalizer

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

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

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

GetSetMethodNormalizer

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

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

PropertyNormalizer

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

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

JsonSerializableNormalizer

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

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

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

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

DateTimeNormalizer
Этот нормализатор преобразует объекты DateTimeInterface (например, DateTime и DateTimeImmutable) в строки. По умолчанию, он использует формат RFC3339.
DataUriNormalizer
Этот нормализатор преобразует объекты SplFileInfo в строку данных URI (data:...), чтобы файлы могли быть встроены в сериализованные данные.
DateIntervalNormalizer
Этот нормализатор преобразует объекты DateInterval в строки. По умолчанию он использует формат P%yY%mM%dDT%hH%iM%sS.
ConstraintViolationListNormalizer

Этот нормализатор преобразует объекты, которые реализуют ConstraintViolationListInterface в список ошибок согласно стандарту RFC 7807.

New in version 4.1: ConstraintViolationListNormalizer появился в Symfony 4.1.

Кодировщики

Кодировщики превращают массивы в форматы и наоборот. Они реализуют 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.

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

Кодировщик JsonEncoder

JsonEncoder кодирует и декодирует в и из строк JSON основываясь на функциях PHP json_encode и json_decode.

Кодировщик CsvEncoder

CsvEncoder кодирует и декодирует в и из CSV.

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

New in version 4.1: Опция as_collection появилась в Symfony 4.1.

Кодировщик XmlEncoder

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

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

1
array('foo' => array(1, 2), 'bar' => true);

XmlEncoder закодирует этот объект так:

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

Учтите, что этот кодировщик понимает ключи, начинающиеся на @ как аттрибуты:

1
2
3
4
5
6
7
$encoder = new XmlEncoder();
$encoder->encode(array('foo' => array('@bar' => 'value')));
// вернёт:
// <?xml version="1.0"?>
// <response>
//     <foo bar="value" />
// </response>

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

New in version 4.1: Опция as_collection появилась в Symfony 4.1.

Tip

XML комментарии по умолчанию игнорируются при декодировании содержимого, но это поведение может быть изменено опциональным аргументом $ignoredNodeTypes конструктора класса XmlEncoder.

New in version 4.1: XML комментарии игнорируются по умолчанию с Symfony 4.1.

Кодировщик YamlEncoder

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

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

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

 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 $name;
    private $members;

    public function setName($name)
    {
        $this->name = $name;
    }

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

    public function setMembers(array $members)
    {
        $this->members = $members;
    }

    public function getMembers()
    {
        return $this->members;
    }
}

class Member
{
    private $name;
    private $organization;

    public function setName($name)
    {
        $this->name = $name;
    }

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

    public function setOrganization(Organization $organization)
    {
        $this->organization = $organization;
    }

    public function getOrganization()
    {
        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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();

$normalizer->setCircularReferenceHandler(function ($object) {
    return $object->getName();
});

$serializer = new Serializer(array($normalizer), array($encoder));
var_dump($serializer->serialize($org, 'json'));
// {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}

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

Компонент Сериализация может определять и ограничивать глубину сериализации. Это особенно полезно при сериализации больших древ. Представьте следующую структуру данных:

 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 $foo;

    /**
     * @var self
     */
    public $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:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    use Symfony\Component\Serializer\Annotation\MaxDepth;
    
    namespace Acme;
    
    class MyObj
    {
        /**
         * @MaxDepth(2)
         */
        public $child;
    
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    Acme\MyObj:
        attributes:
            child:
                max_depth: 2
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <?xml version="1.0" ?>
    <serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
            http://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
    >
        <class name="Acme\MyObj">
            <attribute name="child" max-depth="2" />
        </class>
    </serializer>
    

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$result = $serializer->normalize($level1, null, array('enable_max_depth' => true));
/*
$result = array(
    'foo' => 'level1',
    'child' => array(
            'foo' => 'level2',
            'child' => array(
                    '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
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Serializer;
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\ObjectNormalizer;

class Foo
{
    public $id;

    /**
     * @MaxDepth(1)
     */
    public $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()));
$normalizer = new ObjectNormalizer($classMetadataFactory);
$normalizer->setMaxDepthHandler(function ($foo) {
    return '/foos/'.$foo->id;
});

$serializer = new Serializer(array($normalizer));

$result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true));
/*
$result = array(
    'id' => 1,
    'child' => array(
        'id' => 2,
        'child' => '/foos/3',
    ),
);
*/

New in version 4.1: Метод setMaxDepthHandler() появился в Symfony 4.1.

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

Компонент 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->setSportsperson(false);

$person2 = new Person();
$person2->setName('bar');
$person2->setAge(33);
$person2->setSportsperson(true);

$persons = array($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(
    array(new GetSetMethodNormalizer(), new ArrayDenormalizer()),
    array(new JsonEncoder())
);

$data = ...; // Сериализованные данные из предыдщуего примера
$persons = $serializer->deserialize($data, 'Acme\Person[]', 'json');

Кодировщик XmlEncoder

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

1
array('foo' => array(1, 2), 'bar' => true);

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

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

Ключи массива, начинающиеся с @, считаются XML атрибутами:

1
2
3
4
5
6
7
array('foo' => array('@bar' => 'value'));

// зашифровывается таким образом:
// <?xml version="1.0"?>
// <response>
//     <foo bar="value" />
// </response>

Используйте специальный ключ #, чтобы определить данные узла:

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

// зашифровывается таким образом:
// <?xml version="1.0"?>
// <response>
//     <foo bar="value">
//        baz
//     </foo>
// </response>

Контекст

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

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

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

xml_format_output
Если установлена, как true, форматирует сгенерированный XML с разрывами строчек и отступами.
xml_version
Устанавливает атрибут версии XML (по умолчанию: 1.1).
xml_encoding
Устанавливает атрибут шифроваия XML (по умолчанию: utf-8).
xml_standalone
Добавляет отдельный атрибут в сгенерированном XML (по умолчанию: true).
xml_root_node_name
Устанавливает имя корневого узла (по умолчанию: response).
remove_empty_tags
Если установлена, как true, удаляет все пустые теги в сгенерированном XML.

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

New in version 4.1: Опция default_constructor_arguments появилась в Symfony 4.1.

Если у конструктора класса есть аргументы, что обычно случается в 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
26
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

class MyObj
{
    private $foo;
    private $bar;

    public function __construct($foo, $bar)
    {
        $this->foo = $foo;
        $this->bar = $bar;
    }
}

$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer(array($normalizer));

$data = $serializer->denormalize(
    array('foo' => 'Hello'),
    'MyObj',
    array('default_constructor_arguments' => array(
        'MyObj' => array('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
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

namespace Acme;

class ObjectOuter
{
    private $inner;
    private $date;

    public function getInner()
    {
        return $this->inner;
    }

    public function setInner(ObjectInner $inner)
    {
        $this->inner = $inner;
    }

    public function setDate(\DateTimeInterface $date)
    {
        $this->date = $date;
    }

    public function getDate()
    {
        return $this->date;
    }
}

class ObjectInner
{
    public $foo;
    public $bar;
}

$normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
$serializer = new Serializer(array(new DateTimeNormalizer(), $normalizer));

$obj = $serializer->denormalize(
    array('inner' => array('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.

Рассмотрите приложение, которое определяет абстрактный класс CodeRepository, расширенный классами GitHubCodeRepository и BitBucketCodeRepository. Этот пример показывает, как сериализовать и десериализовать эти объекты:

 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\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
$discriminator->addClassMapping(CodeRepository::class, new ClassDiscriminatorMapping('type', [
    'github' => GitHubCodeRepository::class,
    'bitbucket' => BitBucketCodeRepository::class,
]));

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

$serialized = $serializer->serialize(new GitHubCodeRepository());
// {"type": "github"}

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

Если фабрика метаданных класса включена, как объясняется в разделе Группы атрибутов, вы можете использовать эту упрощённую конфигурацию:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    namespace App;
    
    use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
    
    /**
     * @DiscriminatorMap(typeProperty="type", mapping={
     *    "github"="App\GitHubCodeRepository",
     *    "bitbucket"="App\BitBucketCodeRepository"
     * })
     */
    interface CodeRepository
    {
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    App\CodeRepository:
        discriminator_map:
            type_property: type
            mapping:
                github: 'App\GitHubCodeRepository'
                bitbucket: 'App\BitBucketCodeRepository'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <?xml version="1.0" ?>
    <serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
            http://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
    >
        <class name="App\CodeRepository">
            <discriminator-map type-property="type">
                <mapping type="github" class="App\GitHubCodeRepository" />
                <mapping type="bitbucket" class="App\BitBucketCodeRepository" />
            </discriminator-map>
        </class>
    </serializer>
    

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

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.