Простой REST с Symfony Serializer с Doctrine ORM, денормализация не поддерживается?

У меня есть вопрос/запрос идей относительно десериализация Json в стандартной среде Symfony 4.2.

Подумайте о следующих двух объектах Сообщение и Автор. Каждый Сообщение имеет ровно 1 автор (однонаправленная ассоциация ManyToOne).

Почтовый объект

/**
 * @ORM\Entity()
 */
class Post
{

    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\Column(type = "integer", options = {"unsigned": true})
     * @ORM\GeneratedValue
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type = "text", length=50)
     */
    private $text;

    /**
     * @var Author
     *
     * @ORM\ManyToOne(targetEntity = "Author", fetch = "EAGER")
     */
    private $author;

    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getText(): string
    {
        return $this->text;
    }

    /**
     * @param string $text
     */
    public function setText(string $text): void
    {
        $this->text = $text;
    }

    /**
     * @return Author
     */
    public function getAuthor(): Author
    {
        return $this->author;
    }

    /**
     * @param Author $author
     */
    public function setAuthor(Author $author): void
    {
        $this->author = $author;
    }

}

Авторская сущность

/**
 * @ORM\Entity()
 */
class Author
{

    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\Column(type = "integer", options = {"unsigned": true})
     * @ORM\GeneratedValue
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type = "text", length=50)
     */
    private $name;

    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @param string $name
     */
    public function setName(string $name): void
    {
        $this->name = $name;
    }

}

Теперь давайте создадим простой Контроллер, который позволяет отправлять сообщения ПОЛУЧАТЬ и СООБЩЕНИЕ:

class SimpleController extends AbstractController
{

    /**
     * @Route("/fapi/post/{id}", methods = {"GET", "HEAD"})
     * @param int                 $id
     *
     * @return Response
     */
    public function getEntityAction($id)
    {

        $em = $this->getDoctrine()->getManager();

        $post = $em->getRepository(Post::class)->find($id);

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

        $serializedPost = $serializer->serialize($post, 'json');

        return new Response($serializedPost);
    }

    /**
     * @Route("/fapi/post", methods = {"POST"})
     *
     * @param Request $request
     *
     * @return Response
     */
    public function postEntityAction(Request $request)
    {

        $data = $request->getContent();

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

        $post = $serializer->deserialize($data, Post::class, 'json');

        $em = $this->getDoctrine()->getManager();

        $em->persist($post);
        $em->flush();

        return new Response(''); // return newly created Post

    }

}

Предположим, что в базе данных уже существует сообщение (id: 1). Мы получаем его с помощью ПОЛУЧИТЬ /fapi/post/1. Вот результат:

{"id":1,"text":"Hello World!","author":{"id":1,"name":"Huber"}}

Это прекрасно, Symfony's ObjectNormalizer заботится о нормализации объекта Post и также включает объект Author в результат.

Теперь давайте попробуем создать новый пост с уже существующим Автором с ПОСТ /фапи/пост:

{"id":null,"text":"Hello Universe!","author":{"id":1,"name":"Huber"}}

Теперь мы получаем ошибку от Symfony:

NotNormalizableValueException: The type of the \"id\" attribute for class \"App\Entity\Post\" must be one of \"int\" (\"NULL\" given)

Я ценю ошибку и понимаю, почему это происходит, но нет ли встроенного способа сериализации Symfony, чтобы убедиться, что автоматически сгенерированные значения (с помощью ORM, с помощью конструктора, такого как UUID) не учитываются в этом «стандартном случае». ".

Известные мне решения:

  1. вообще не отправлять я бы с запросом (но, согласно моему пониманию REST, полное представление объектов должно быть заменено)
  2. использование GetSetMethodNormalizer вместо ObjectNormalizer (поскольку нет сеттера для я бы, который работает, но имеет несколько других недостатков).
  3. исключение я бы из контекста денормализации (например, с помощью 'ignored_attributes')
  4. создание собственного нормализатора, чтобы позаботиться об этой ситуации

А пока давайте попробуем перейти от ObjectNormalizer к GetSetMethodNormalizer, чтобы преодолеть нашу ошибку, и снова попробуем POST.

Теперь мы получаем другую ошибку:

FatalThrowableError: Argument 1 passed to App\Entity\Post::setAuthor() must be an instance of App\Entity\Author, array given

Согласно документации GetSetMethodNormalizer, дальнейшая нормализация не происходит при установке значений в денормализации.

Итак, еще раз я оцениваю ошибку и понимаю, что происходит.

Платформа API решает эти проблемы с помощью специального система денормализации.

Я понимаю и ценю, что Symfony и Doctrine — это два разных пакета, но, так как они так часто комбинируются (и действительно обычно прекрасно работают вместе):

Есть ли какой-либо встроенный или известный способ в сериализации Symfony, который в основном позволяет мне в приведенных выше «стандартных случаях» добиться того, чтобы эта цепочка работала без ошибок и без индивидуальной реализации нормализаторов или других?

База данных => Doctrine ORM => Symfony GET object => json => POST тот же json => Symfony => Doctrine ORM => База данных

P.S. Еще небольшой пример:

GET /fapi/post/1 только доставляет

{"id":1,"text":"Hello World!","author":{"id":1,"name":"Huber"}}

потому что ассоциация ManyToOne извлекается с ОЖИДАНИЕМ, если по умолчанию установлено значение LAZY, оно становится:

{"id":1,"text":"Hello World!","author":{"id":1,"name":"Huber","__initializer__":null,"__cloner__":null,"__isInitialized__":true}}

потому что ObjectNormalizer сериализует объект Doctrine Proxy.

P.P.S: я знаю о JMSSerializerBundle и много раз его использовал, но мне полностью нравится и я поддерживаю Symfony в том, что у него есть «собственный» компонент Serializer.

Я не понимаю, почему вы отправляете "id":null, потому что организация не может иметь идентификатор со значением null (хотя объект может). однако ваша сериализация возражений по доверенности - это другое дело.

Jakumi 20.03.2019 17:13

спасибо @Jakumi, я понял твою точку зрения. Тем не менее, я говорю немного шире с точки зрения Symfony Serializer, поддерживающего Doctrine Entities.

LBA 20.03.2019 20:26

Я бы предложил использовать десериализацию для существующих объектов, таких как symfony.com/doc/current/components/…, и сохранить разные маршруты для создания и обновления сущностей (как это и должно быть, кстати). однако основная проблема заключается в том, что вам каким-то образом нужно решить, как обращаться с подсущностями. Я не понимаю, как это должно быть частью независимого сериализатора symfony, когда это довольно специфично для доктрины. Надеюсь, вы поняли, к чему я клоню: в обязанности (де)сериализатора не входит волшебное угадывание правильного объекта.

Jakumi 20.03.2019 21:49

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

Jakumi 20.03.2019 21:50

@Jakumi Я понимаю, и, как вы видели, я говорю об объектах (не заинтересован, например, в создании нового автора с сообщением и т. д.) - и да, это не должно быть стандартным поведением, но так же, как Serializer использует, например. Метаданные для нормализации, почему не может и, возможно, даже не должен DoctrineNormalizer обрабатывать объекты Doctrine и ассоциированные объекты, например? Видите, что я пытаюсь сказать?

LBA 20.03.2019 22:05

да, я тебя полностью понимаю. связанный и, возможно, полезный: stackoverflow.com/questions/37741197/… (один из более поздних ответов предоставляет пользовательский нормализатор...)

Jakumi 20.03.2019 22:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
2
6
1 289
0

Другие вопросы по теме