首页 > 解决方案 > Symfony/Doctrine 无限递归,同时从具有反向多对一关系的数据库中获取

问题描述

语境

在一个简单的 Symfony 项目中,我创建了两个实体ProductCategory,它们通过 a@ManyToOne@OneToMany与 Doctrine Annotations 的关系相关联。一个类别可以有多个产品,一个产品与一个类别相关。我已经在Category表中手动插入数据。

当我使用Category实体存储库获取数据并用 显示它时var_dump(...),会发生无限递归。当我返回包含这些数据的 JSON 响应时,它只是空的。它应该准确检索我手动插入的数据。

您是否知道如何在不删除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;
    }
}

标签: symfonydoctrine-orm

解决方案


我假设您var_dump用于调试目的。出于调试目的使用dumpor ddwhich 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_encodeJsonResponse对象中的快捷方式(或者使用 symfony/serializer 代替)。json_encode另一方面,除非对象实现(见下文) ,否则序列化给定对象的公共属性。由于几乎所有实体通常都具有私有属性,因此结果通常是一个空对象序列化。JsonSerializable

有很多选项可供选择,但本质上您需要解决无限递归的问题。恕我直言的标准选项是:

在这种情况下,一个安全数组是一个,它只包含字符串、数字以及字符串和数字的(嵌套)数组,这本质上意味着丢失所有实际对象。

可能还有其他选择,但我发现这些是最方便的。我通常更喜欢这个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 序列化方案。对于任何其他方式,您将不得不使用其他选项......无论如何这几乎总是正确的。


推荐阅读