Как использовать Serializer
Дата обновления перевода 2025-02-18
Как использовать Serializer
Symfony предоставляет сериализатор для преобразования структур данных из
одного формата в PHP-объекты и наоборот.
Это чаще всего используется при создании API или общении со сторонними API.
Сериализатор может преобразовать входящий JSON-запрос
в PHP-объект, который потребляется вашим приложением. Затем, при генерировании
ответа, вы можете использовать сериализатор для преобразования
PHP-объектов обратно в ответ JSON.
Его также можно использовать, например, для загрузки данных
конфигурации CSV как PHP-объектов, или даже для преобразования между
форматами (например, YAML в XML).
Установка
В приложениях, использующих Symfony Flex , выполните эту команду,
чтобы установить пакет Symfony serializer
перед его использованием:
1
$ composer require symfony/serializer-pack
Note
Пакет сериализатора также устанавливает некоторые часто используемые дополнительные
зависимости компонента Serializer. При использовании этого компонента
вне фреймворка Symfony, вы можете захотеть начать с пакета
symfony/serializer
и установить дополнительные зависимости, если они вам
нужны.
See also
Популярной альтернативой компоненту Symfony Serializer является сторонняя библиотека JMS serializer.
Сериализация объекта
Для этого примера предположим, что в вашем проекте существует следующий класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// src/Model/Person.php
namespace App\Model;
class Person
{
public function __construct(
private int $age,
private string $name,
private bool $sportsperson
) {
}
public function getAge(): int
{
return $this->age;
}
public function getName(): string
{
return $this->name;
}
public function isSportsperson(): bool
{
return $this->sportsperson;
}
}
Если вы хотите преобразовать объекты этого типа в JSON-структуру (например
отправить их через API-ответ), получите сервис serializer
, используя
тип параметра SerializerInterface :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/Controller/PersonController.php
namespace App\Controller;
use App\Model\Person;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\SerializerInterface;
class PersonController extends AbstractController
{
public function index(SerializerInterface $serializer): Response
{
$person = new Person('Jane Doe', 39, false);
$jsonContent = $serializer->serialize($person, 'json');
// $jsonContent содержит {"name":"Jane Doe","age":39,"sportsperson":false}
return JsonResponse::fromJsonString($jsonContent);
}
}
Первый параметр serialize() является объектом, который нужно сериализировать, а второй - используется для выбора соответствующего кодировщика (то есть формата), в этом случае - JsonEncoder.
Tip
Когда ваш класс контроллера расширяет AbstractController
(как в вышеприведенном примере), вы можете
упростить свой контроллер, используя метод
json()
для создания JSON-ответа из объекта с помощью Serializer:
1 2 3 4 5 6 7 8 9 10
class PersonController extends AbstractController
{
public function index(): Response
{
$person = new Person('Jane Doe', 39, false);
// если Serializer недоступен, будет использоваться json_encode()
return $this->json($person);
}
}
Использование Serializer в шаблонах Twig
Вы также можете сериализировать объекты в любом шаблоне Twig с помощью фильтра
serialize
:
1
{{ person|serialize(format = 'json') }}
Смотрите справочник twig , чтобы узнать больше.
Десериализация объекта
API часто также требуют преобразования отформатированного тела запроса (например, JSON) в объект PHP. Этот процесс называется десериализация (также известный как "гидратация"):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// src/Controller/PersonController.php
namespace App\Controller;
// ...
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
class PersonController extends AbstractController
{
// ...
public function create(Request $request, SerializerInterface $serializer): Response
{
if ('json' !== $request->getContentTypeFormat()) {
throw new BadRequestException('Unsupported content format');
}
$jsonData = $request->getContent();
$person = $serializer->deserialize($jsonData, Person::class, 'json');
// ... сделать что-то с $person и вернуть ответ
}
}
В этом случае, deserialize() требует трех параметров:
- Данные, которые нужно расшифровать
- Имя класса, в который будет расшифрована эта информация
- Название кодировщика, который используется для преобразования данных в массив (то есть формат ввода)
При отправке запроса к этому контроллеру (например
{«first_name»: «John Doe», «age»:54, «sportsperson»:true}
), сериализатор
создаст новый экземпляр Person
и установит свойства в значения из предоставленного JSON.
Note
По умолчанию, дополнительные атрибуты, которые не сопоставлены с
денормализованным объектом, будут проигнорированы компонентом Serializer. Например,
если запрос к вышеприведенному контроллеру содержит {..., «city»: «Paris"}
,
поле city
будет проигнорировано. Вы также можете вызвать исключение в
этих случаях с помощью контекста serializer ,
о котором вы узнаете позже.
See also
Вы также можете десериализовать данные в существующий экземпляр объекта (например
при обновлении данных). Смотрите
Десериализация в существующий объект .
Процесс сериализации: нормализаторы и кодировщики
Сериализатор использует двухэтапный процесс при (де)сериализации объектов:
В обоих направлениях данные всегда сначала преобразуются в массив. Это разделяет процесс на две отдельные задачи:
- Нормализаторы
- Эти классы преобразуют объекты в массивы и наоборот. Они выполняют тяжелую работу, выясняя, какие свойства класса должны быть сериализированы, какое значение они имеют и какое название они должны иметь.
- Кодировщики
-
Кодировщики преобразуют массивы в определенный формат и наоборот.
Каждый кодировщик точно знает, как анализировать и генерировать определенный формат, например JSON или XML.
Внутренне класс Serializer
использует отсортированный список нормализаторов и
один кодировщик для конкретного формата при (де)сериализации объекта.
Существует несколько нормализаторов, сконфигурированных в сервисе Serializer
по умолчанию. Самым важным нормализатором является
ObjectNormalizer. Этот
нормализатор использует отображение и компонент PropertyAccess`
для преобразования между любым объектом и массивом. Вы узнаете больше об
этом и других нормализаторах позже.
Сериализатор по умолчанию также сконфигурирован с некоторыми кодировщиками, которые охватывают распространенные форматы, используемые HTTP-приложениями:
Прочтите больше об этих кодировщиках и их конфигурации в Кодировщики Serializer.
Tip
Проэкт API Platform предоставляет кодировщики для более продвинутых форматов:
Контекст Serializer
Сериализатор, его нормализаторы и кодировщики конфигурируются с помощью контекста
сериализатора. Этот контекст можно сконфигурировать в нескольких местах:
- Глобально через конфигурацию фреймворка
- Во время сериализации/десериализации `
- Для конкретного свойства .
Вы можете использовать все три варианта одновременно. Если одна и та же настройка
сконфигурирована в нескольких местах, последняя в списке выше будет переопределять
предыдущую (например, настройка для конкретного свойства переопределяет то, что
сконфигурировано глобально).
Сконфигурируйте контекст по умолчанию
Вы можете сконфигурировать контекст по умолчанию в конфигурации фреймворка,
например, запретить дополнительные поля при десериализации:
1 2 3 4 5
# config/packages/serializer.yaml
framework:
serializer:
default_context:
allow_extra_attributes: false
Передайте контекст во время сериализации/десериализации
Вы также можете сконфигурировать контекст для одного вызова
serialize()
/ deserialize()
. Например, вы можете пропустить свойства
со значением null
только для одного вызова сериализатора:
1 2 3 4 5 6 7 8
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
// ...
$serializer->serialize($person, 'json', [
AbstractObjectNormalizer::SKIP_NULL_VALUES => true
]);
// следующие вызовы к serialize() НЕ будут пропускать значения null
Использование строителей контекста
Чтобы определить контекст (де)сериализации, вы можете использовать "строителей контекста", которые являются объектами, которые помогают вам создавать этот контекст, предоставляя автозаполнение, валидацию и документацию:
1 2 3 4 5
use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder;
$contextBuilder = (new DateTimeNormalizerContextBuilder())
->withFormat('Y-m-d H:i:s');
$serializer->serialize($something, 'json', $contextBuilder->toArray());
Каждый нормализатор/кодировщик имеет свой связанный строитель контекста .
Чтобы создать более сложный контекст (де)сериализации, вы можете сделать из них цепочку,
используя метод withContext()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder;
use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder;
$initialContext = [
'custom_key' => 'custom_value',
];
$contextBuilder = (new ObjectNormalizerContextBuilder())
->withContext($initialContext)
->withGroups(['group1', 'group2']);
$contextBuilder = (new CsvEncoderContextBuilder())
->withContext($contextBuilder)
->withDelimiter(';');
$serializer->serialize($something, 'csv', $contextBuilder->toArray());
See also
Вы также можете создать собственных строителей контекста, чтобы иметь автозаполнение, валидацию и документацию для ваших пользовательских значений контекста.
Сконфигурируйте контекст в конкретном свойстве
Наконец, вы также можете сконфигурировать значение контекста для конкретного свойства объекта. Например, для конфигурации формата времени и даты:
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/Model/Person.php
// ...
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
class Person
{
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
public \DateTimeImmutable $createdAt;
// ...
}
Note
При использовании YAML или XML файлы карт должны быть размещены в одном из этих мест:
- Все файлы
*.yaml
и*.xml
- в каталогеconfig/serializer/
. - Файл
serialization.yaml
илиserialization.xml
- в каталоге пакетаResources/config/
. - Все файлы
*.yaml
и*.xml
- в каталоге пакетаResources/config/serialization/
.
Вы также можете указать контекст, специфичный для нормализации или денормализации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// src/Model/Person.php
// ...
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
class Person
{
#[Context(
normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'],
denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339],
)]
public \DateTimeImmutable $createdAt;
// ...
}
Вы также можете ограничить использование контекста для некоторых групп :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// src/Model/Person.php
// ...
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
class Person
{
#[Groups(['extended'])]
#[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
#[Context(
context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED],
groups: ['extended'],
)]
public \DateTimeImmutable $createdAt;
// ...
}
Атрибут может повторяться столько раз, сколько нужно, для одного свойства.
Контекст без группы всегда применяется первым. Затем контекст для
соответствующих групп объединяется в предоставленном порядке.
Если вы повторяете один и тот же контекст в нескольких свойствах, воспользуйтесь атрибутом#[Context]
в вашем классе, чтобы применить эту конфигурацию контекста ко всем свойствам
класса :
1 2 3 4 5 6 7 8 9 10 11 12 13 14
namespace App\Model;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
#[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
#[Context(
context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED],
groups: ['extended'],
)]
class Person
{
// ...
}
Сериализация в или из PHP-массивов
Serializer по умолчанию также может быть
использован для выполнения только одного шага двухшагового процесса сериализации
с помощью соответствующего интерфейса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
// ...
class PersonController extends AbstractController
{
public function index(DenormalizerInterface&NormalizerInterface $serializer): Response
{
$person = new Person('Jane Doe', 39, false);
// использовать normalize(), чтобы преобразовать PHP-объект в массив
$personArray = $serializer->normalize($person, 'json');
// ...и denormalize(), чтобы преобразовать массив обратно в PHP-объект
$personCopy = $serializer->denormalize($personArray, Person::class);
// ...
}
public function json(DecoderInterface&EncoderInterface $serializer): Response
{
$data = ['name' => 'Jane Doe'];
// использовать encode(), чтобы преобразовать PHP-массивы в другой формат
$json = $serializer->encode($data, 'json');
// ...и decode(), чтобы преобразовать любой формат просто в PHP-массивы (вместо объектов)
$data = $serializer->decode('{"name":"Charlie Doe"}', 'json');
// $data contains ['name' => 'Charlie Doe']
}
}
Игнорирование свойств
ObjectNormalizer
нормализует все свойства объекта и все
методы, начинающиеся с get*()
, has*()
, is*()
и can*()
.
Некоторые свойства или методы никогда не должны быть сериализованы. Вы можете
исключить их с помощью атрибута #[Ignore]
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Model/Person.php
namespace App\Model;
use Symfony\Component\Serializer\Attribute\Ignore;
class Person
{
// ...
#[Ignore]
public function isPotentiallySpamUser(): bool
{
// ...
}
}
Свойство potentiallySpamUser
теперь никогда не будет сериализована:
1 2 3 4 5 6 7 8 9 10 11 12 13
use App\Model\Person;
// ...
$person = new Person('Jane Doe', 32, false);
$json = $serializer->serialize($person, 'json');
// $json містить {"name":"Jane Doe","age":32,"sportsperson":false}
$person1 = $serializer->deserialize(
'{"name":"Jane Doe","age":32,"sportsperson":false","potentiallySpamUser":false}',
Person::class,
'json'
);
// значення "potentiallySpamUser" ігнорується
Игнорирование атрибутов с использованием контекста
Вы также можете передать массив имен атрибутов для игнорирования во время выполнения с помощью
опций контекста ignored_attributes
:
1 2 3 4 5 6 7 8 9
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
// ...
$person = new Person('Jane Doe', 32, false);
$json = $serializer->serialize($person, 'json',
[
AbstractNormalizer::IGNORED_ATTRIBUTES => ['age'],
]);
// $json содержит {"name":"Jane Doe","sportsperson":false}
Однако это может быстро привести к непредсказуемым последствиям, если использовать его чрезмерно. См. следующий раздел о группах сериализации для лучшего решения.
Выбор конкретных свойств
Вместо того, чтобы исключать свойство или метод во всех ситуациях, вам может потребоваться исключить некоторые свойства в одном месте, но сериализовать их в другом. Группы - удобный способ достичь этого.
Вы можете добавить атрибут #[Groups]
к вашему классу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Model/Person.php
namespace App\Model;
use Symfony\Component\Serializer\Attribute\Groups;
class Person
{
#[Groups(["admin-view"])]
private int $age;
#[Groups(["public-view"])]
private string $name;
#[Groups(["public-view"])]
private bool $sportsperson;
// ...
}
Теперь вы можете выбрать, какие группы использовать при сериализации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
$json = $serializer->serialize(
$person,
'json',
['groups' => 'public-view']
);
// $json содержит {"name":"Jane Doe","sportsperson":false}
// вы также можете передать массив групп
$json = $serializer->serialize(
$person,
'json',
['groups' => ['public-view', 'admin-view']]
);
// $json содержит {"name":"Jane Doe","age":32,"sportsperson":false}
// или использовать специальное значение «*» для выбора всех групп
$json = $serializer->serialize(
$person,
'json',
['groups' => '*']
);
// $json содержит {"name":"Jane Doe","age":32,"sportsperson":false}
Использование контекста сериализации
Наконец, вы также можете воспользоваться опцией контекста attributes
для выбора
свойств во время выполнения:
1 2 3 4 5 6 7
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
// ...
$json = $serializer->serialize($person, 'json', [
AbstractNormalizer::ATTRIBUTES => ['name', 'company' => ['name']]
]);
// $json содержит {"name":"Dunglas","company":{"name":"Les-Tilleuls.coop"}}
Доступны только атрибуты, которые не игнорируются .
Если установлены группы сериализации, можно использовать только атрибуты, разрешенные
этими группами.
Работа с массивами
Сериализатор может работать с массивами объектов. Сериализация массивов работает так же, как и сериализация одного объекта:
1 2 3 4 5 6 7 8 9 10
use App\Model\Person;
// ...
$person1 = new Person('Jane Doe', 39, false);
$person2 = new Person('John Smith', 52, true);
$persons = [$person1, $person2];
$JsonContent = $serializer->serialize($persons, 'json');
// $jsonContent содержит [{"name":"Jane Doe","age":39,"sportsman":false},{"name":"John Smith","age":52,"sportsman":true}]
Чтобы десериализировать список объектов, к типу параметра нужно добавить []
:
1 2 3 4
// ...
$jsonData = ...; // сериализованные данные JSON из предыдущего примера
$persons = $serializer->deserialize($JsonData, Person::class.'[]', 'json');
Для вложенных классов вы должны добавить тип PHPDoc к свойству, конструктору или сеттеру:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// src/Model/UserGroup.php
namespace App\Model;
class UserGroup
{
/**
* @param Person[] $members
*/
public function __construct(
private array $members,
) {
}
// или, если вы используете сеттер
/**
* @param Person[] $members
*/
public function setMembers(array $members): void
{
$this->members = $members;
}
// ...
}
Tip
Serializer также поддерживает типы массивов, которые используются в статическом анализе, такие как
list<Person>
и array<Person>
. Убедитесь, что установлены пакеты
phpstan/phpdoc-parser
и phpdocumentor/reflection-docblock
(они являются частью
пакета symfony/serializer-pack
).
Десериализация с использованием вложенных структур
Некоторые API могут предоставлять многословные вложенные структуры, которые вы захотите упростить в объекты PHP. Например, представьте себе следующий ответ в формате JSON:
1 2 3 4 5 6 7 8 9
{
"id": "123",
"profile": {
"username": "jdoe",
"personal_information": {
"full_name": "Jane Doe"
}
}
}
Возможно, вы захотите сериализировать эту информацию в один объект PHP типа:
1 2 3 4 5 6
class Person
{
private int $id;
private string $username;
private string $fullName;
}
Используйте #[SerializedPath]
, чтобы указать путь к вложенному свойству,
используя валидный синтаксис PropertyAccess:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
namespace App\Model;
use Symfony\Component\Serializer\Attribute\SerializedPath;
class Person
{
private int $id;
#[SerializedPath('[profile][username]')]
private string $username;
#[SerializedPath('[profile][personal_information][full_name]')]
private string $fullName;
}
Warning
SerializedPath
нельзя использовать в сочетании с SerializedName
для одного свойства.
Атрибут #[SerializedPath]
также применяется к сериализации PHP-объекта:
1 2 3 4 5 6
use App\Model\Person;
// ...
$person = new Person(123, 'jdoe', 'Jane Doe');
$jsonContent = $serializer->serialize($person, 'json');
// $jsonContent містить {"id":123,"profile":{"username":"jdoe","personal_information":{"full_name":"Jane Doe"}}}
Преобразование имен свойств при сериализации и десериализации
Иногда сериализированные атрибуты нужно называть иначе, чем свойства или методы геттеров/сеттеров PHP-классов. Этого можно с помощью преобразователей имен.
Сервис сериализатора использует
MetadataAwareNameConverter.
С помощью этого преобразователя имен вы можете изменить имя атрибута, используя
атрибут #[SerializedName]
:
1 2 3 4 5 6 7 8 9 10 11 12
// src/Model/Person.php
namespace App\Model;
use Symfony\Component\Serializer\Attribute\SerializedName;
class Person
{
#[SerializedName('customer_name')]
private string $name;
// ...
}
Это пользовательское отображение используется для преобразования имен свойств во время сериализации и десериализации объектов:
1 2 3 4
// ...
$json = $serializer->serialize($person, 'json');
// $json містить {"customer_name":"Jane Doe", ...}
See also
Вы также можете создать пользовательский класс преобразователя имен. Подробнее об этом можно прочитать в в Как создать пользовательский преобразователь имен.
CamelCase в snake_case
Во многих форматах принято использовать нижнее подчеркивание для разделения слов
(также известно как snake_case). Однако в приложениях Symfony принято использовать
camelCase для именования свойств.
Symfony предоставляет встроенный преобразователь имен, предназначенный для преобразования между стилями
snake_case и CamelCase при сериализации и десериализации.
Вы можете использовать его вместо преобразователя имен, учитывающего метаданные,
установив настройку name_converter
в значение
serializer.name_converter.camel_case_to_snake_case
:
1 2 3 4
# config/packages/serializer.yaml
framework:
serializer:
name_converter: 'serializer.name_converter.camel_case_to_snake_case'
Нормализаторы Serializer
По умолчанию сервис сериализатора сконфигурирован с такими нормализаторами
(в порядке приоритета):
- UnwrappingDenormalizer
- Можно использовать для денормализации только части ввода, подробнее об об этом далее в этой статье .
- ProblemNormalizer
- Нормализует ошибки FlattenException согласно спецификации проблем API RFC 7807.
- UidNormalizer
-
Нормализует объекты, которые расширяют AbstractUid.
Формат нормализации по умолчанию для объектов, реализующих Uuid - это формат RFC 4122 (пример:
d9e7a184-5d5b-11ea-a62a-3499710062d0
). Формат нормализации по умолчанию для объектов, реализующих Ulid - это формат Base 32 (пример:01E439TP9XJZ9RPFH3T1PYBCR8
). Вы можете изменить формат строки, задав опцию контекста сериализатораUidNormalizer::NORMALIZATION_FORMAT_KEY
какUidNormalizer::NORMALIZATION_FORMAT_BASE_58
,UidNormalizer::NORMALIZATION_FORMAT_BASE_32
илиUidNormalizer::NORMALIZATION_FORMAT_RFC_4122
.Также он может денормализовать строки
uuid
илиulid
до Uuuuid` или Ulid. Формат не имеет значения. - DateTimeNormalizer
-
Это нормализует между объектами DateTimeInterface (например DateTime и DateTimeImmutable) и строками, целыми числами или числами с плавающей запятой. По умолчанию, он преобразует их в строки, используя формат RFC 3339.
ИспользуйтеDateTimeNormalizer::FORMAT_KEY
иDateTimeNormalizer::TIMEZONE_KEY
для изменения формата.Для преобразования объектов в целые числа или числа с плавающей запятой, установите в сериализаторе опцию контекста
DateTimeNormalizer::CAST_KEY
в значениеint
илиfloat
.7.1
Опция контекста
DateTimeNormalizer::CAST_KEY
была представлена в Symfony 7.1. - ConstraintViolationListNormalizer
-
Этот нормализатор преобразует объекты, реализующие
ConstraintViolationListInterface, в список
ошибок в соответствии со стандартом RFC 7807. - DateTimeZoneNormalizer
- Этот нормализатор преобразует между объектами DateTimeZone и строками, которые представляют название часового пояса согласно списку часовых поясов PHP.
- DateIntervalNormalizer
-
Это нормализует между объектами и строками DateInterval.
По умолчанию используется формат
P%yY%mM%dDT%hH%iM%sS
. Используйте опциюDateIntervalNormalizer::FORMAT_KEY
, чтобы изменить его. - FormErrorNormalizer
-
Этот нормализатор работает с классами, которые реализуют FormInterface.
Он будет получать ошибки из формы и нормализовать их согласно спецификации
проблем API RFC 7807. - TranslatableNormalizer
-
Этот нормализатор преобразует объекты, реализующие TranslatableInterface в переведенную строку с помощью translator.
Вы можете определить локаль, которую следует использовать для перевода объекта, установив
опцию контекстаTranslatableNormalizer::NORMALIZATION_LOCALE_KEY
. - BackedEnumNormalizer
-
Этот нормализатор преобразует между исчислением BackedEnum и строками или целыми числами.
По умолчанию, если данные не являются валидным исчислением, вызывается исключение. Если вы вместо этого хотите получить
null
, вы можете установить опциюBackedEnumNormalizer::ALLOW_INVALID_VALUES
. - DataUriNormalizer
-
Этот нормализатор преобразует между объектами SplFileInfo и
строкой данных URI (
data:...
) таким образом, что файлы могут быть встроены в сериализованные данные. Цей нормалізатор перетворює між об'єктами SplFileInfo та - JsonSerializableNormalizer
-
Этот нормализатор работает с классами, которые реализуют JsonSerializable.
Он вызовет метод JsonSerializable::jsonSerialize() и затем далее нормализует результат. Это означает, что вложенные классы JsonSerializable также будут нормализованы.
Этот нормализатор особенно полезен, когда вы хотите постепенно мигрировать с существующей кодовой базы с использованием простой json_encode на Symfony Serializer, позволяя вам смешивать нормализаторы для разных классов.
В отличие от json_encode, можно обрабатывать циклические ссылки.
- ArrayDenormalizer
- Этот денормализатор превращает массив массивов в массив объектов (с заданным типом). Смотрите Работа с массивами .
- ObjectNormalizer
-
Это самый мощный нормализатор по умолчанию, используемый для любых объектов, которые не могут быть нормализованы другими нормализаторами.
Он использует Компонент PropertyAccess для чтения и записи в объекте. Это позволяет ему получать доступ к свойствам непосредственно или с помощью геттеров, сеттеров, хассеров, иссеров, каннеров,
добавителей и излучателей. Имена генерируются путем удаленияget
,set
,has
,is
,add
илиremove
из названия метода и преобразования первой буквы в строчную (например,getFirstName()
->firstName
).При денормализации поддерживаются использование конструктора, а также найденные методы.
Danger
Всегда проверяйте, чтобы DateTimeNormalizer
было зарегистрировано во время
сериализации классов DateTime
или DateTimeImmutable
, чтобы избежать
чрезмерного использования памяти и раскрытия внутренних данных.
Встроенные нормализаторы
Кроме нормализаторов, зарегистрированных по умолчанию (см. предыдущий раздел),
компонент serializer также предоставляет некоторые дополнительные нормализаторы. Вы можете зарегистрировать
их, определив сервис и обозначив его тегом serializer.normalizer .
Например, для использования CustomNormalizer
вам следует определить сервис
вроде:
1 2 3 4 5 6 7 8 9
# config/services.yaml
services:
# ...
# если вы используете автоконфигурацию, тег будет применен автоматически
Symfony\Component\Serializer\Normalizer\CustomNormalizer:
tags:
# зарегистрировать нормализатор с высоким приоритетом (вызывается раньше)
- { name: 'serializer.normalizer', priority: 500 }
- CustomNormalizer
- Этот нормализатор вызывает метод в объекте PHP при нормализации. PHP-объект должен реализовывать NormalizableInterface и/или DenormalizableInterface.
- GetSetMethodNormalizer
-
Этот нормализатор является альтернативой стандартному
ObjectNormalizer
. Он читает содержание класса, вызывая "геттеры" (публичные методы, начинающиеся сget
,has
,is
илиcan
). Он будет денормализовать данные, вызывая конструктор и "сеттеры" (общедоступные методы, начинающиеся сset
).Объекты нормализуются к карте имен и значений (имена генерируются путем удаления префикса
get
из имени метода и преобразования первой буквы в строчную; например,getFirstName()
->firstName
). - PropertyNormalizer
-
Это еще одна альтернатива
ObjectNormalizer
. Этот нормализатор напрямую читает и записывает публичные свойства, а также частные и защищенные свойства (как из класса, так и из всех его родительских классов), используя PHP-отражение. Он поддерживает вызов конструктора во время процесса денормализации.Объекты нормализуются в карту имен свойств в значение свойств.
Вы также можете ограничить нормализатор, чтобы он использовал только свойства с определенной видимостью (например, только публичные свойства) с помощью опции контекста
PropertyNormalizer::NORMALIZE_VISIBILITY
. Вы можете установить любую комбинацию константPropertyNormalizer::NORMALIZE_PUBLIC
,PropertyNormalizer::NORMALIZE_PROTECTED
иPropertyNormalizer::NORMALIZE_PRIVATE
:1 2 3 4 5 6 7 8 9 10
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; // ... $json = $serializer->serialize($person, 'json', [ // серіалізувати лише публічні властивості PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC, // серіалізувати публічні та захищені властивості PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED, ]);
Отладка Serializer
Воспользуйтесь командой debug:serializer
для сброса метаданных сериализатора для
заданного класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
$ php bin/console debug:serializer 'App\Entity\Book'
App\Entity\Book
---------------
+-------------+------------------------------------------------------------+
| Свойство | Опции |
+-------------+------------------------------------------------------------+
| name | [ |
| | "groups" => [ |
| | "book:read", |
| | "book:write", |
| | ], |
| | "maxDepth" => 1, |
| | "serializedName" => "book_name", |
| | "serializedPath" => null, |
| | "ignore" => false, |
| | "normalizationContexts" => [], |
| | "denormalizationContexts" => [] |
| | ] |
| isbn | [ |
| | "groups" => [ |
| | "book:read", |
| | ], |
| | "maxDepth" => null, |
| | "serializedName" => null, |
| | "serializedPath" => "[data][isbn]", |
| | "ignore" => false, |
| | "normalizationContexts" => [], |
| | "denormalizationContexts" => [] |
| | ] |
+-------------+------------------------------------------------------------+
Продвинутая сериализация
Пропуск значений null
По умолчанию, Serializer будет сохранять свойства, содержащие значение null.
Вы можете изменить это поведение, установив опцию контекста AbstractObjectNormalizer::SKIP_NULL_VALUES
в значение true
:
1 2 3 4 5 6 7 8 9 10
class Person
{
public string $name = 'Jane Doe';
public ?string $gender = null;
}
$jsonContent = $serializer->serialize(new Person(), 'json', [
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
]);
// $jsonContent содержит {"name":"Jane Doe"}
Работа с неинициализированными свойствами
В PHP типизированные свойства имеют состояние uninitialized
, которое отличается.
от стандартного null
нетипизированных свойств. Когда вы пытаетесь получить
доступ к типизированному свойству до того, как придать ему явное значение, вы получаете ошибку.
Чтобы избежать ошибки при сериализации или нормализации объекта с неинициализированными
свойствами, по умолчанию ObjectNormalizer
перехватывает эти ошибки и игнорирует
следующие свойства.
Вы можете отключить такое поведение, установив опцию контекста
AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES
в значение
false
:
1 2 3 4 5 6 7 8 9 10
class Person {
public string $name = 'Jane Doe';
public string $phoneNumber; // uninitialized
}
$jsonContent = $normalizer->serialize(new Dummy(), 'json', [
AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false,
]);
// вызывает Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException
// так как ObjectNormalizer не может прочитать неинициализированные свойства
Note
Использование PropertyNormalizer
или GetSetMethodNormalizer
с опцией контекста AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES
, установленной
как false
, вызовент экземпляр \Error
, если заданный объект имеет
неинициализированные свойства, так как нормализаторы не могут их прочитать
(напрямую или через методы геттера/иссера).
Рfбота c цикличными ссылками
Цикличные ссылки распространены при работе с ассоциированными объектами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
class Organization
{
public function __construct(
private string $name,
private array $members = []
) {
}
public function getName(): string
{
return $this->name;
}
public function addMember(Member $member): void
{
$this->members[] = $member;
}
public function getMembers(): array
{
return $this->members;
}
}
class Member
{
private Organization $organization;
public function __construct(
private string $name
) {
}
public function getName(): string
{
return $this->name;
}
public function setOrganization(Organization $organization): void
{
$this->organization = $organization;
}
public function getOrganization(): Organization
{
return $this->organization;
}
}
Чтобы избежать бесконечных циклов, нормализаторы вызывают исключение CircularReferenceException, когда встречается такой случай:
$organization = new Organization('Les-Tilleuls.coop'); $member = new Member('Kévin');
$organization->addMember($member); $member->setOrganization($organization);
$jsonContent = $serializer->serialize($organization, 'json'); // вызывает CircularReferenceException
Ключ circular_reference_limit
в контексте задает количество
раз, которые будет сериализирован тот же объект, прежде чем считать его цикличной
ссылкой. Значение по умолчанию – 1
.
Вместо того чтобы вызывать исключение, цикличные ссылки можно также обрабатывать с помощью пользовательских вызываемых. Это особенно полезно при сериализации сущностей с уникальными идентификаторами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use Symfony\Component\Serializer\Exception\CircularReferenceException;
$context = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (object $object, ?string $format, array $context): string {
if (!$object instanceof Organization) {
throw new CircularReferenceException('A circular reference has been detected when serializing the object of class "'.get_debug_type($object).'".');
}
// сериализовать вложенную Organization только с именем (а не с членами)
return $object->getName();
},
];
$jsonContent = $serializer->serialize($organization, 'json', $context);
// $jsonContent содержит {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}
Работа с глубиной сериализации
Сериализатор может обнаруживать вложенные объекты одного класса и ограничивать глубину сериализации. Это полезно для древовидных структур, где один и тот же объект может быть вложен несколько раз.
Например, допустим структуру данных семейного дерева:
// ... class Person { // ...
- public function __construct(
- private string $name, private ?self $mother
) { }
public function getName(): string { return $this->name; }
public function getMother(): ?self { return $this->mother; }
// ...
}
// ... $greatGrandmother = new Person('Elizabeth', null); $grandmother = new Person('Jane', $greatGrandmother); $mother = new Person('Sophie', $grandmother); $child = new Person('Joe', $mother);
Вы можете указать максимальную глубину для определенного свойства. Например, вы можете установить максимальную
глубину как 1
, чтобы всегда сериализировать только чью-то мать
(а не бабушку и т.д.):
1 2 3 4 5 6 7 8 9 10 11 12
// src/Model/Person.php
namespace App\Model;
use Symfony\Component\Serializer\Attribute\MaxDepth;
class Person
{
#[MaxDepth(1)]
private ?self $mother;
// ...
}
Чтобы ограничить глубину сериализации, в контексте необходимо установить ключ
AbstractObjectNormalizer::ENABLE_MAX_DEPTH
в значение true
(или в контексте по умолчанию, определенном в файле framework.yaml
):
1 2 3 4 5 6 7 8 9 10
// ...
$greatGrandmother = new Person('Elizabeth', null);
$grandmother = new Person('Jane', $greatGrandmother);
$mother = new Person('Sophie', $grandmother);
$child = new Person('Joe', $mother);
$jsonContent = $serializer->serialize($child, null, [
AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true
]);
// $jsonContent содержит {"name":"Joe","mother":{"name":"Sophie"}}
Вы также можете сконфигурировать пользовательское вызываемое, которое будет использовано, когда будет достигнута максимальная глубина. Это может быть использовано, например, для возвращения уникального идентификатора следующего вложенного объекта, вместо того чтобы опускать свойство:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
// ...
$greatGrandmother = new Person('Elizabeth', null);
$grandmother = new Person('Jane', $greatGrandmother);
$mother = new Person('Sophie', $grandmother);
$child = new Person('Joe', $mother);
// все параметры обратного вызова являются необязательными (вы можете опустить те, которые не используете)
$maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, ?string $format = null, array $context = []): ?string {
// вернуть только имя следующего человека в дереве
return $innerObject instanceof Person ? $innerObject->getName() : null;
};
$jsonContent = $serializer->serialize($child, null, [
AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler,
]);
// $jsonContent содержит {"name":"Joe","mother":{"name":"Sophie","mother":"Jane"}}
Использование обратных вызовов для сериализации свойств с помощью экземпляров объектов
Во время сериализации вы можете задать обратный вызов для форматирования определенного свойства объекта. Это можно использовать вместо определения контекста для группы :
1 2 3 4 5 6 7 8 9 10 11 12 13
$person = new Person('cordoval', 34);
$person->setCreatedAt(new \DateTime('now'));
$context = [
AbstractNormalizer::CALLBACKS => [
// все параметры обратного вызова являются необязательными (вы можете опустить те, которые не используете)
'createdAt' => function (object $attributeValue, object $object, string $attributeName, ?string $format = null, array $context = []) {
return $attributeValue instanceof \DateTime ? $attributeValue->format(\DateTime::ATOM) : '';
},
],
];
$jsonContent = $serializer->serialize($person, 'json', $context);
// $jsonContent содержит {"name":"cordoval","age":34,"createdAt":"2014-03-22T09:43:12-0500"}
Продвинутая десериализация
Требовать все свойства
По умолчанию, Serializer добавит null
к свойствам, которые можно обнулить, если
для них не указаны параметры. Вы можете изменить это поведение,
установив опцию контекста AbstractNormalizer::REQUIRE_ALL_PROPERTIES
в значение true
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class Person
{
public function __construct(
public string $firstName,
public ?string $lastName,
) {
}
}
// ...
$data = ['firstName' => 'John'];
$person = $serializer->deserialize($data, Person::class, 'json', [
AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true,
]);
// вызывает Symfony\Component\Serializer\Exception\MissingConstructorArgumentException
Сбор ошибок типа во время денормализации
При денормализации полезной нагрузки к объекту с типизированными свойствами, вы получите исключение, если полезная нагрузка содержит свойства, не имеющие такого же типа, что и объект.
Используйте COLLECT_DENORMALIZATION_ERRORS
, чтобы собрать все исключения.
сразу и чтобы получить объект частично денормализованным:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
try {
$person = $serializer->deserialize($jsonString, Person::class, 'json', [
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
]);
} catch (PartialDenormalizationException $e) {
$violations = new ConstraintViolationList();
/** @var NotNormalizableValueException $exception */
foreach ($e->getErrors() as $exception) {
$message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType());
$parameters = [];
if ($exception->canUseMessageForUser()) {
$parameters['hint'] = $exception->getMessage();
}
$violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null));
}
// ... вернуть список нарушений пользователю
}
Десериализация в существующий объект
Сериализатор также можно использовать для обновления существующего объекта. Вы можете сделать
это с помощью опции контекста сериализатора object_to_populate
:
1 2 3 4 5 6 7 8 9
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
// ...
$person = new Person('Jane Doe', 59);
$serializer->deserialize($jsonData, Person::class, 'json', [
AbstractNormalizer::OBJECT_TO_POPULATE => $person,
]);
// вместо возвращения нового объекта, обновляется $person
Note
Опция AbstractNormalizer::OBJECT_TO_POPULATE
используется только для
объекта верхнего уровня. Если этот объект является корнем древовидной структуры,
все дочерние элементы, существующие в нормализованных данных, будут преобразованы
с новыми экземплярами.
Когда опция контекста AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE
установлена в значение true
, имеющиеся дочерние объекты корня OBJECT_TO_POPULATE
обновляются из нормализованных данных, вместо того, чтобы денормализатор
преобразовал их. Это работает только для одиночных дочерних объектов, а не для
массивов объектов. Они все равно будут заменены, если присутствуют в
нормализованных данных.
Десериализация интерфейсов и абстрактных классов
При работе с ассоциированными объектами свойство иногда ссылается на интерфейс или абстрактный класс. При десериализации этих свойств Serializer должен знать, какой конкретный класс будет инициализирован. Это делается с помощью дискриминаторного отображения классов.
Представьте, что есть интерфейс InvoiceItemInterface
, который реализован
объектами Product
и Shipping
. При сериализации объекта, сериализатор
добавит дополнительный "атрибут дискриминатора". Этот атрибут содержит
или product
, или shipping
. Карта класса дискриминатора сопоставляет
эти имена типов с реальными именами классов PHP при десериализации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
namespace App\Model;
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
#[DiscriminatorMap(
typeProperty: 'type',
mapping: [
'product' => Product::class,
'shipping' => Shipping::class,
]
)]
interface InvoiceItemInterface
{
// ...
}
Со сконфигурированной картой дискриминатора, сериализатор теперь может выбрать
правильный класс для свойств, типизированных как InvoiceItemInterface
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class InvoiceLine
{
public function __construct(
private InvoiceItemInterface $invoiceItem
) {
$this->invoiceItem = $invoiceItem;
}
public function getInvoiceItem(): InvoiceItemInterface
{
return $this->invoiceItem;
}
// ...
}
// ...
$invoiceLine = new InvoiceLine(new Product());
$jsonString = $serializer->serialize($invoiceLine, 'json');
// $jsonString содержит {"type":"product",...}
$invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json');
// $invoiceLine содержит новый InvoiceLine(new Product(...))
Частичная десериализация ввода (распаковка)
Сериализатор всегда десериализирует всю строку ввода в значение PHP. При подключении
к посторонним API вам часто нужна только определенная часть возвращаемого ответа.
Чтобы избежать десериализации всего ответа, вы можете использовать UnwrappingDenormalizer и "распаковать" данные ввода:
$jsonData = '{"result":"success","data":{"person":{"name": "Jane Doe","age":57}}}'; $data = $serialiser->deserialize($jsonData, Object::class, [ UnwrappingDenormalizer::UNWRAP_PATH => '[data][person]', ]); // $data - Person(name: 'Jane Doe', age: 57)
unwrap_path
- это путь свойства
компонента PropertyAccess, примененный к денормализованному массиву.
Работа с аргументами конструктора
Если в конструкторе класса определены аргументы, как это обычно бывает с Value Objects, сериализатор будет сопоставлять имена параметров с десериализированными атрибутами. Если некоторых параметров не хватает, будет вызвано MissingConstructorArgumentsException.
В этих случаях используйте опцию контекста default_constructor_arguments
, чтобы
определить значения по умолчанию для отсутствующих параметров:
use AppModelPerson; use SymfonyComponentSerializerNormalizerAbstractNormalizer; // ...
$jsonData = '{"age":39,"name":"Jane Doe"}'; $person = $serializer->deserialize($jsonData, Person::class, 'json', [ AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [ Person::class => ['sportsperson' => true], ], ]); // $person - это Person(name: 'Jane Doe', age: 39, sportsperson: true);
Рекурсивная денормализация и безопасность типов
Если доступен ExtractorTypeExtractor
, нормализатор также
проверит, соответствуют ли данные для денормализации типу свойства (даже для
примитивных типов). Например, если предоставлено string
, а тип свойства - int
, то будет вызвано
UnexpectedValueException.
Принудительное применение типов свойств можно отключить с помощью
установки опции контекста сериализатора
ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT
в значение true
.
Работа с булевыми занчениями
7.1
Опция контекстп AbstractNormalizer::FILTER_BOOL
была представлена в Symfony 7.1.
PHP рассматривает много разных значений как истинные или ложные. Например, строки true
,
1
и yes
считаются истинными, тогда как false
, 0
и no
считаются ложными.
При десериализации компонент Serializer может позаботиться об этом
автоматически. Это можно сделать с помощью опции контекста AbstractNormalizer::FILTER_BOOL
:
1 2 3 4 5 6 7
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
// ...
$person = $serializer->denormalize(['sportsperson' => 'yes'], Person::class, context: [
AbstractNormalizer::FILTER_BOOL => true
]);
// $person содержит экземпляр Person с sportsperson, установленным как true
Этот контекст заставляет процесс десериализации вести себя как функция
filter_var с флажком FILTER_VALIDATE_BOOL
.
Конфигурация кэша метаданных
Метаданные для сериализатора автоматически кэшируются для повышения производительности приложения.
По умолчанию сериализатор использует пул кэша cache.system
, который конфигурируется
с помощью опции cache.system .