symfony - Symfony/Doctrine 无限递归,同时从具有反向多对一关系的数据库中获取
问题描述
语境
在一个简单的 Symfony 项目中,我创建了两个实体Product
和Category
,它们通过 a@ManyToOne
和@OneToMany
与 Doctrine Annotations 的关系相关联。一个类别可以有多个产品,一个产品与一个类别相关。我已经在Category
表中手动插入数据。
当我使用Category
实体存储库获取数据并用 显示它时var_dump(...)
,会发生无限递归。当我返回包含这些数据的 JSON 响应时,它只是空的。它应该准确检索我手动插入的数据。
您是否知道如何在不删除Category
实体中的反向关系的情况下避免此错误?
我试过的
- 在关系的一侧、另一侧和两侧添加 Doctrine Annotation fetch="LAZY" 。
- 使用 Doctrine 在数据库中插入
Category
对象以查看数据库连接是否正常。是的。 - 删除关系的反面。它有效,但这不是我想要的。
代码片段
控制器
dummy/src/Controller/DefaultController.php
...
$entityManager = $this->getDoctrine()->getManager();
$repository = $entityManager->getRepository(Category::class);
// ===== PROBLEM HERE =====
//var_dump($repository->findOneByName('house'));
//return $this->json($repository->findOneByName('house'));
...
实体
dummy/src/Entity/Category.php
<?php
namespace App\Entity;
use App\Repository\CategoryRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=CategoryRepository::class)
*/
class Category
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @ORM\OneToMany(targetEntity=Product::class, mappedBy="category", fetch="LAZY")
*/
private $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return Collection|Product[]
*/
public function getProducts(): Collection
{
return $this->products;
}
public function addProduct(Product $product): self
{
if (!$this->products->contains($product)) {
$this->products[] = $product;
$product->setCategory($this);
}
return $this;
}
public function removeProduct(Product $product): self
{
if ($this->products->contains($product)) {
$this->products->removeElement($product);
// set the owning side to null (unless already changed)
if ($product->getCategory() === $this) {
$product->setCategory(null);
}
}
return $this;
}
}
dummy/src/Entity/Product.php
<?php
namespace App\Entity;
use App\Repository\ProductRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=ProductRepository::class)
*/
class Product
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity=Category::class, inversedBy="products", fetch="LAZY")
* @ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
}
解决方案
我假设您var_dump
用于调试目的。出于调试目的使用dump
or dd
which is fromsymfony/debug
并且应该已经dev
默认启用。两者都dump
应该dd
及时中止无限递归。(很多 symfony/doctrine 对象/服务有循环引用或者只是很多引用的对象。)dump
将给定的 php var(s) 添加到分析器(分析器栏中的目标标记符号)或输出。dd
添加给定的 var(s) likedump
但也结束该过程(so d ump 和d ie)。- 在生产中永远不要使用 dump/dd/var_dump,而是正确地序列化你的数据。
其次,$this->json
本质上是打包json_encode
到JsonResponse
对象中的快捷方式(或者使用 symfony/serializer 代替)。json_encode
另一方面,除非对象实现(见下文) ,否则序列化给定对象的公共属性。由于几乎所有实体通常都具有私有属性,因此结果通常是一个空对象序列化。JsonSerializable
有很多选项可供选择,但本质上您需要解决无限递归的问题。恕我直言的标准选项是:
- 使用可以处理循环引用(导致无限递归/循环)的symfony 序列化程序,从而将对象转换为安全数组。但是,结果可能仍然不是您喜欢的...
- 在您的实体上实施
JsonSerializable
并小心避免递归添加子对象。 - 自己从对象构建一个安全数组,传递给
$this->json
(“手动方法”)。
在这种情况下,一个安全数组是一个,它只包含字符串、数字以及字符串和数字的(嵌套)数组,这本质上意味着丢失所有实际对象。
可能还有其他选择,但我发现这些是最方便的。我通常更喜欢这个JsonSerializable
选项,但这是一个品味问题。一个例子是:
class Category implements \JsonSerializable { // <-- new implements!
// ... your entity stuff
public function jsonSerialize() {
return [
'id' => $this->id,
'name' => $this->name,
'products' => $this->products->map(function(Product $product) {
return [
'id' => $product->getId(),
'name' => $product->getName(),
// purposefully excluding category here!
];
})->toArray(),
];
}
}
添加此代码后,您的代码应该可以正常工作。对于开发人员,您应该始终dump
如前所述使用,一切$this->json
都会正常工作。这就是为什么我通常更喜欢这个选项。但是,需要注意的是:这种方式只能为类别提供一个json 序列化方案。对于任何其他方式,您将不得不使用其他选项......无论如何这几乎总是正确的。
推荐阅读
- flutter - 颤振:未处理的异常:SocketException:操作系统错误:没有到主机的路由,
- android - Firebase 更新嵌套数据而不删除
- python - 烧瓶 api 没有返回 xml
- c# - 如何异步/等待按钮单击,不仅仅是在等待页面上等待,而是在返回之前等待页面上执行的操作
- javascript - 如何在数组中的条目内搜索字符串
- java - 无法使用来自 Selenium 中的 Superclass 的 final 关键字的变量(声明的 WebElement)
- javascript - 加载函数的不同目录
- python - 在使用 str.findall 时获取 pandas 系列中的索引
- python-3.x - 有没有办法通过在其他列上找到最佳过滤器来保留最大不同值
- kframework - 什么时候对 K 个配置单元进行类型检查?