我有一个问题/请求,关于在标准Symfony 4.2环境中反序列化Json的想法。
考虑以下两个实体“Post”和“Author”。每个“Post”都有一个“Author”(单向ManyToOne Association)。
“Post Entity”
现在让我们创建一个简单的控制器,它允许获取和发布帖子:
假设数据库中已经存在一篇帖子(id:1)。我们使用 GET /fapi/post/1 命令来获取它。这是结果:
这很好,Symfony的ObjectNormalizer负责规范化Post对象,并将Author对象包含在结果中。
现在让我们尝试使用POST /fapi/post创建一个已存在作者的新文章:
现在我们收到了Symfony的错误信息:
我知道的解决方案有:
现在我们又得到了另一个错误。
致命的可抛出错误:传递给App\Entity\Post::setAuthor()的第1个参数必须是App\Entity\Author的实例,但传递了一个数组。
根据GetSetMethodNormalizer的文档,在反序列化中设置值时不会进行进一步的规范化。
所以我很感激这个错误并理解发生了什么。
API Platform通过自定义的反规范化系统来处理这些问题。
我理解和赞赏Symfony和Doctrine是两个不同的包,但是它们经常结合在一起(事实上通常非常好地协作):
在上述“标准情况”下,是否有任何内置或已知的方式可以让我实现此链路运行而没有错误,并且不需要自定义实现规范化器或其他内容?
数据库 => Doctrine ORM => Symfony GET对象 => json => POST相同的json => Symfony => Doctrine ORM => 数据库
附加小例子:
GET /fapi/post/1只提供
由于ManyToOne关联被急加载,如果设置了默认的LAZY,则变成:
因为ObjectNormalizer序列化Doctrine代理对象。
另外,我知道JMSSerializerBundle,也经常使用它,但我完全支持Symfony拥有自己的序列化器组件。
考虑以下两个实体“Post”和“Author”。每个“Post”都有一个“Author”(单向ManyToOne Association)。
“Post Entity”
/**
* @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)。我们使用 GET /fapi/post/1 命令来获取它。这是结果:
{"id":1,"text":"Hello World!","author":{"id":1,"name":"Huber"}}
这很好,Symfony的ObjectNormalizer负责规范化Post对象,并将Author对象包含在结果中。
现在让我们尝试使用POST /fapi/post创建一个已存在作者的新文章:
{"id":null,"text":"Hello Universe!","author":{"id":1,"name":"Huber"}}
现在我们收到了Symfony的错误信息:
我很感激这个错误并理解它发生的原因,但Symfony序列化有没有内置的方法可以确保在这种“标准情况”下不考虑自动生成的值(由ORM或构造函数生成的UUID等)。NotNormalizableValueException:类“App\Entity\Post”的“id”属性的类型必须是“int”(给出了“NULL”)
我知道的解决方案有:
- 根本不发送请求中的id(但根据我对REST的理解,应该交换对象的完整表示)
- 使用GetSetMethodNormalizer代替ObjectNormalizer(因为没有适用于id的Setter,但有其他几个缺陷)。
- 从反序列化上下文中排除id(例如通过“ignored_attributes”)
- 创建自定义规范化器以处理此情况
现在我们又得到了另一个错误。
致命的可抛出错误:传递给App\Entity\Post::setAuthor()的第1个参数必须是App\Entity\Author的实例,但传递了一个数组。
根据GetSetMethodNormalizer的文档,在反序列化中设置值时不会进行进一步的规范化。
所以我很感激这个错误并理解发生了什么。
API Platform通过自定义的反规范化系统来处理这些问题。
我理解和赞赏Symfony和Doctrine是两个不同的包,但是它们经常结合在一起(事实上通常非常好地协作):
在上述“标准情况”下,是否有任何内置或已知的方式可以让我实现此链路运行而没有错误,并且不需要自定义实现规范化器或其他内容?
数据库 => Doctrine ORM => Symfony GET对象 => json => POST相同的json => Symfony => Doctrine ORM => 数据库
附加小例子:
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代理对象。
另外,我知道JMSSerializerBundle,也经常使用它,但我完全支持Symfony拥有自己的序列化器组件。
"id":null
,因为实体不能具有值为 null 的 id(但对象可能会)。不过你的代理反序列化是另一回事。 - Jakumi