У меня есть вопрос / запрос на идеи относительно десериализации 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;
}
}
Автор Entity
/**
* @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;
}
}
Теперь давайте создадим простой Контроллер , который позволяет GET и POST сообщений:
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
}
}
Предположим, что в базе данных уже существует Post (id: 1). Мы получаем его с помощью GET / FAPI / Post / 1 . Это результат:
{"id":1,"text":"Hello World!","author":{"id":1,"name":"Huber"}}
Это прекрасно, ObjectNormalizer Symfony заботится о нормализации объекта Post и также включает в себя объект Author.
Теперь давайте попробуем создать новый пост с уже существующим автором с помощью POST / fapi / post :
{"id":null,"text":"Hello Universe!","author":{"id":1,"name":"Huber"}}
Теперь мы получаем сообщение от Symfony:
NotNormalizableValueException: тип атрибута \ "id \" для класса \ "App \ Entity \ Post \" должен быть одним из \ "int \" (указана \ "NULL \")
Я ценю ошибку и понимаю, почему это происходит, но нет ли встроенного способа сериализации Symfony, чтобы гарантировать, что автоматически сгенерированные значения (с помощью ORM, с помощью конструктора, такого как UUID) в этом не учитываются "стандартный случай".
Решения, которые мне известны:
- вообще не отправляет id с запросом (но, согласно моему пониманию REST, следует полностью заменить представление объектов)
- с использованием GetSetMethodNormalizer вместо ObjectNormalizer (так как для id нет Setter, который работает, но имеет несколько других недостатков).
- исключая id из контекста денормализации (например, с помощью ignored_attributes)
- создание собственного нормализатора для решения этой ситуации
А сейчас давайте попробуем перейти от ObjectNormalizer к GetSetMethodNormalizer, чтобы преодолеть нашу ошибку и повторить попытку POST.
Теперь мы получаем еще одну ошибку:
FatalThrowableError: Аргумент 1, передаваемый в App \ Entity \ Post :: setAuthor (), должен быть экземпляром App \ Entity \ Author, массив указан
Согласно документации GetSetMethodNormalizer, дальнейшая нормализация не происходит при установке значений в денормализации.
Итак, я снова ценю ошибку и понимаю, что происходит.
API Platform решает эти проблемы с помощью пользовательской системы денормализации
Я понимаю и ценю, что Symfony и Doctrine - это два разных пакета, , но , так как они объединяются так часто (и, действительно, обычно прекрасно играют вместе):
Существует ли какой-либо встроенный или известный способ в сериализации Symfony, который в принципе позволяет мне в вышеупомянутых «стандартных случаях» добиться того, чтобы эта цепочка работала без ошибок и без специальной реализации нормализаторов или других?
База данных => Doctrine ORM => Symfony GET объект => json => POST same json => Symfony => Doctrine ORM => База данных
P.S .: Еще один маленький пример:
GET / fapi / post / 1 доставляет только
{"id":1,"text":"Hello World!","author":{"id":1,"name":"Huber"}}
поскольку сопоставление ManyToOne выбирается EAGER, если установлено значение 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.