У меня есть вопрос/запрос идей относительно десериализация 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) не учитываются в этом «стандартном случае». ".
Известные мне решения:
А пока давайте попробуем перейти от 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.
спасибо @Jakumi, я понял твою точку зрения. Тем не менее, я говорю немного шире с точки зрения Symfony Serializer, поддерживающего Doctrine Entities.
Я бы предложил использовать десериализацию для существующих объектов, таких как symfony.com/doc/current/components/…, и сохранить разные маршруты для создания и обновления сущностей (как это и должно быть, кстати). однако основная проблема заключается в том, что вам каким-то образом нужно решить, как обращаться с подсущностями. Я не понимаю, как это должно быть частью независимого сериализатора symfony, когда это довольно специфично для доктрины. Надеюсь, вы поняли, к чему я клоню: в обязанности (де)сериализатора не входит волшебное угадывание правильного объекта.
если вы добавите некоторые обратные вызовы для определенных объектов, чтобы фактически создавать ссылки (у доктрины есть эта, возможно, соответствующая функциональность), когда предоставляется только идентификатор, и для создания объекта, когда нет. но это сложно, если смешать. Я бы посоветовал не обновлять несколько сущностей разного типа одновременно.
@Jakumi Я понимаю, и, как вы видели, я говорю об объектах (не заинтересован, например, в создании нового автора с сообщением и т. д.) - и да, это не должно быть стандартным поведением, но так же, как Serializer использует, например. Метаданные для нормализации, почему не может и, возможно, даже не должен DoctrineNormalizer обрабатывать объекты Doctrine и ассоциированные объекты, например? Видите, что я пытаюсь сказать?
да, я тебя полностью понимаю. связанный и, возможно, полезный: stackoverflow.com/questions/37741197/… (один из более поздних ответов предоставляет пользовательский нормализатор...)






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