Symfony 4 сериализует объект без отношений

Я должен регистрировать изменения каждой сущности. У меня есть прослушиватель, который прослушивает события доктрины preRemove, postUpdate и postDelete. Моя сущность AccessModule имеет отношения:

App\Entity\AccessModule.php

/**
 * @ORM\OneToMany(targetEntity="App\Entity\AccessModule", mappedBy="parent")
 * @ORM\OrderBy({"id" = "ASC"})
 */
private $children;

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\AccessModule", inversedBy="children")
 * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
 */
private $parent;

/**
 * @ORM\ManyToMany(targetEntity="App\Entity\AccessModuleRoute", inversedBy="access_modules")
 * @ORM\JoinTable(name="access_routes",
 *     joinColumns={@ORM\JoinColumn(name="access_module_id", referencedColumnName="id")},
 *     inverseJoinColumns={@ORM\JoinColumn(name="route_id", referencedColumnName="id")})
 *
 */
private $routes;

в слушателе: App\EventListener\EntityListener.php

use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;


    $encoders = [new XmlEncoder(), new JsonEncoder()];
    $normalizer = new ObjectNormalizer();

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

    $this->serializer = new Serializer([$normalizer], $encoders);


public function createLog(LifecycleEventArgs $args, $action){
    $em = $args->getEntityManager();
    $entity = $args->getEntity();
    if ($this->tokenStorage->getToken()->getUser()) {
        $username = $this->tokenStorage->getToken()->getUser()->getUsername();
    } else {
        $username = 'anon'; // TODO Remove anon. set null value
    }

    $log = new Log();
//      $log->setData('dddd')
        $log->setData($this->serializer->serialize($entity, ''json)
            ->setAction($action)
            ->setActionTime(new \DateTime())
            ->setUser($username)
            ->setEntityClass(get_class($entity));
        $em->persist($log);
        $em->flush();
    }

У меня проблемы с сериализацией. Когда я использую $log->setData($entity), у меня возникают проблемы с Circular. Когда я делаю сериализацию $log->setData($this->serializer->serialize($entity, ''json), у меня полно отношений с детьми родителей, с детьми детей. В результате я получаю полное дерево :/ Я хочу получить

Ожидать

[
 'id' => ID,
 'name' => NAME,
 'parent' => parent_id // ManyToOne, I'd like get its id
 'children' => [$child_id, $child_id, $child_id] // array of $id of children array collection
]

(конечно, это черновик, прежде чем кодировать его в json)

Как я могу получить ожидаемые данные без полных отношений?


person nicram    schedule 01.02.2018    source источник


Ответы (3)


То, что вы ищете, называется группами сериализации: здесь и здесь.

Теперь позвольте мне объяснить, как это работает. Это довольно просто. Скажем, у вас есть Post Entity:

class Post
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"default"})
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User\User")
     * @Groups({"default"})
     */
    private $author;
}

И у вас также есть User Entity:

class User
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"default"})
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=40)
     * @Groups({"default"})
     */
    private $firstName;

    /**
     * @ORM\Column(type="string", length=40)
     */
    private $lastName;
}

Сообщение может иметь автора (пользователя), но я не хочу каждый раз возвращать все данные пользователя. Меня интересует только id и имя.

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

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

Теперь, как сообщить Symfony об этом?

Когда вы подготавливаете/настраиваете свою службу сериализации, вам просто нужно предоставить определенные группы, подобные этой:

return $this->serializer->serialize($data, 'json', ['groups' => ['default']]);

Было бы неплохо создать какой-нибудь сервис-оболочку вокруг родного сериализатора symfony, чтобы вы могли упростить весь процесс и сделать его более пригодным для повторного использования.

Также убедитесь, что сериализатор настроен правильно, иначе он не будет учитывать эту группу.

Это также один из способов (среди прочего) «обработки» циклических ссылок.

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

person Robert    schedule 12.02.2018
comment
Отличный ответ. Я хотел бы добавить только одну маленькую вещь: чтобы сообщить Symfony, что вы хотите прочитать группы из аннотации, вы должны сделать это. $classMetadataFactory = new ClassMetadataFactory (новый AnnotationLoader (новый AnnotationReader())); Определение classMetadataFactory и передача его в качестве параметра вашему ObjectNormalizer. $normalizer = новый ObjectNormalizer($classMetadataFactory); symfony.com/doc/current/components/ - person SpicyTacos23; 20.11.2019

ignored_attributes обеспечивает быстрый и простой способ выполнить то, что вы ищете.

$serializer->serialize($object, 'json', ['ignored_attributes' => ['ignored_property']]); 

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

use Acme\Person;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

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

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

$serializer = new Serializer([$normalizer], [$encoder]);
$serializer->serialize($person, 'json', ['ignored_attributes' => ['age']]); 

Документация: https://symfony.com/doc/current/components/serializer.html#ignoring-attributes

person Brennan Walsh    schedule 17.12.2018

Протестировано в Symfony 4.1, вот документация, которая действительно работает https://symfony.com/blog/new-in-symfony-2-7-serialization-groups

В объяснении Роберта https://stackoverflow.com/a/48756847/579646 отсутствует $classMetadataFactory для работы. Вот мой код:

    $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
    $encoders = [new JsonEncoder()];
    $normalizer = new ObjectNormalizer($classMetadataFactory);
    $normalizer->setCircularReferenceLimit(2);
    // Add Circular reference handler
    $normalizer->setCircularReferenceHandler(function ($object) {
        return $object->getId();
    });
    $normalizers = [$normalizer];
    $serializer = new Serializer($normalizers, $encoders);
    $jsonContent = $serializer->serialize($jobs, 'json', array('groups' => ['default']));

    return JsonResponse::fromJsonString($jsonContent);
person max4ever    schedule 06.09.2018
comment
Хорошо подмечено. Я просто пытался кратко объяснить, в чем проблема, не вдаваясь в подробности. Но да, вы можете соединить вещи разными способами и свести к минимуму количество кода, который вам придется писать позже. - person Robert; 02.04.2019