glaphire
@glaphire
PHP developer

Как работать с кастомной сериализацией/нормализацией?

По мотивам моего прошлого вопроса хочу уточнить, как правильно.

Есть энтити NearEarthObject и класс пагинации PaginatedCollection, который хранит массив этих энтити.
Если я создавала нормалайзер для PaginatedCollection и вызывала в контроллере сериализацию как
$serializer = new Serializer([new PaginatedCollectionNormalizer], [new JsonEncoder]);
$serializer->serialize($paginatedCollection);

То не сериализировался список NearEarthObject, т.е. просто выводился пустой массив. Если я писала другой Normalizer, нп. PropertyNormalizer или ObjectNormalizer, то сыпался ворох ошибок "невозможно нормализовать объект".
Мне пришлось заставить NearEarthObject и PaginatedCollection реализовывать интерфейс JsonSerializable, чтобы получать адекватный итоговый сериализированный объект.

Код:
App\Entity\NearEarthObject.php
App\Pagination\PaginatedCollection.php
App\Pagination\PaginatedFactory.php
NearEarthObjectController.php
PaginatedCollectionNormalizer.php
Структура респонса

{
    "items": [
        {
            "id": 39,
            "name": "523707 (2014 JM25)",
            "reference": 2523707,
            "speed": 11950.225447854,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-08 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 49,
            "name": "9162 Kwiila (1987 OA)",
            "reference": 2009162,
            "speed": 81164.553763328,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-07 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 53,
            "name": "468730 (2010 MN51)",
            "reference": 2468730,
            "speed": 58929.965618653,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-06 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 54,
            "name": "510073 (2010 JF88)",
            "reference": 2510073,
            "speed": 62117.643247045,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-06 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 68,
            "name": "494713 (2005 OU2)",
            "reference": 2494713,
            "speed": 78875.305112096,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-05 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 70,
            "name": "228502 (2001 TE2)",
            "reference": 2228502,
            "speed": 64971.943374331,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-05 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 71,
            "name": "36284 (2000 DM8)",
            "reference": 2036284,
            "speed": 109370.58384093,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-04 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 72,
            "name": "(2002 CB19)",
            "reference": 3114030,
            "speed": 32990.860071831,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-04 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 74,
            "name": "68347 (2001 KB67)",
            "reference": 2068347,
            "speed": 34199.451888508,
            "is_hazardous": true,
            "date": {
                "date": "2020-08-04 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        },
        {
            "id": 82,
            "name": "512245 (2016 AU8)",
            "reference": 2512245,
            "speed": 32235.678141793,
            "is_hazardous": false,
            "date": {
                "date": "2020-08-03 00:00:00.000000",
                "timezone_type": 3,
                "timezone": "UTC"
            }
        }
    ],
    "total": 66,
    "count": 10,
    "links": {
        "self": "/neo/hazardous?page=1",
        "first": "/neo/hazardous?page=1",
        "last": "/neo/hazardous?page=7",
        "next": "/neo/hazardous?page=2"
    }
}



Вопросы: как нужно поступать правильно - энтити же должны сериализироваться по-умолчанию, почему, когда они попадают в класс-обертку, сериализация перестает работать? Нужен ли в этой цепочке нормалайзер, если он ничего не дает?
  • Вопрос задан
  • 595 просмотров
Решения вопроса 1
glaphire
@glaphire Автор вопроса
PHP developer
Огромное спасибо BoShurik за комментарий о пакете symfony/property-info
Решение проблемы несериализуемых вложенных элементов внутри объекта:
Установить все пакеты
symfony/serializer
symfony/property-access
symfony/property-info
phpdocumentor/reflection-docblock


Внутри класса, где есть свойство - массив объектов, прописать аннотацию
<?php

namespace App\Pagination;

use App\Entity\NearEarthObject;

class PaginatedCollection
{
    /**
     * @param NearEarthObject[] $items
     */
    private $items;
    ...


Внутри метода, где происходит сериализация, прописать:
$extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
        $converter = new CamelCaseToSnakeCaseNameConverter();
        $normalizers = [
            new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d']),
            new ArrayDenormalizer(),
            new ObjectNormalizer(null, $converter, null, $extractor),
        ];
        $encoders = [new JsonEncoder()];

        $serializer = new Serializer($normalizers, $encoders);
        $serializedPaginatedCollection = $serializer->serialize($paginatedCollection, 'json');


Итоговый контроллер
<?php

namespace App\Controller;

use App\Entity\NearEarthObject;
use App\Pagination\PaginationFactory;
use App\Repository\NearEarthObjectRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

class NearEarthObjectController extends AbstractController
{
    private EntityManagerInterface $entityManager;
    /**
     * @var PaginationFactory
     */
    private $paginationFactory;

    public function __construct(EntityManagerInterface $entityManager, PaginationFactory $paginationFactory)
    {
        $this->entityManager = $entityManager;
        $this->paginationFactory = $paginationFactory;
    }

    /**
     * @Route("/neo/hazardous", name="neo_hazardous", methods={"GET"})
     */
    public function hazardousAction(Request $request, ObjectNormalizer $objectNormalizer)
    {
        /**
         * @var NearEarthObjectRepository $nearEarthObjectRepository
         */
        $nearEarthObjectRepository = $this
            ->entityManager
            ->getRepository(NearEarthObject::class);

        //TODO: add getting is_hazardous=1
        $queryBuilder = $nearEarthObjectRepository->findAllQueryBuilder();

        $paginatedCollection = $this
            ->paginationFactory
            ->createCollection($queryBuilder, $request, 'neo_hazardous');

        $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
        $converter = new CamelCaseToSnakeCaseNameConverter();
        $normalizers = [
            new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d']),
            new ArrayDenormalizer(),
            new ObjectNormalizer(null, $converter, null, $extractor),
        ];
        $encoders = [new JsonEncoder()];

        $serializer = new Serializer($normalizers, $encoders);
        $serializedPaginatedCollection = $serializer->serialize($paginatedCollection, 'json');

        return new Response($serializedPaginatedCollection, 200, [
            'Content-Type' => 'application/json',
        ]);
    }
}


Источники:
Issue на github, который показывает решение
The Symfony Serializer: a great, but complex component

UPD. Еще раз спасибо BoShurik за уточнения - если изначально поставить symfony/serializer-pack, то все пакеты будут установлены сразу, аннотация @param NearEarthObject[] $items не нужна, а кусок
$extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
        $converter = new CamelCaseToSnakeCaseNameConverter();
        $normalizers = [
            new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d']),
            new ArrayDenormalizer(),
            new ObjectNormalizer(null, $converter, null, $extractor),
        ];
        $encoders = [new JsonEncoder()];

        $serializer = new Serializer($normalizers, $encoders);
        $serializedPaginatedCollection = $serializer->serialize($paginatedCollection, 'json');

        return new Response($serializedPaginatedCollection, 200, [
            'Content-Type' => 'application/json',
        ]);


сводится к
return $this->json($paginatedCollection);
и сериалайзер выполняет всю работу автоматически
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы