摆脱复杂实体关系的循环引用(Symfony 6.4)

问题描述 投票:0回答:1

当我想要规范化对象宽度 ObjectNormalizer 和 Serializer 时,我的应用程序中有一个循环引用错误,但我无法摆脱它。 我已经这样做过好几次了,但是这个应用程序比我编写的其他应用程序更复杂。

我的应用程序是一个用于内容写作的杂志后端。为简单起见,假设内容可以是文章、分组(内容组)或 Diaporama(幻灯片)。

变得更复杂的是,由于所有内容都有几个共同的字段,因此所有内容实体都以 oneToOne 关系与母实体 GlobalContent 相关。

每个内容(除了Groupement)都可以与0-n Groupement相关。所以,如果你明白了,GlobalContent 有两个关系 Groupement:GlobalContent 可以是 Groupement(OneToOne 可空关系),GlobalContent 可以属于 0-n Groupement


(为了简化起见,由于每个实体都与单个 GlobalContent 实体相关,因此我将以一个名称命名这两个实体。例如:GlobalContent-Article)

因此 GlobalContent-文章“今年夏天的吃馅饼大赛”与 GlobalContent-Groupement“今年夏天的趣味活动”相关(与其他几个内容相关,而其他内容又与其他几个 GlobalContent-Groupement 相关)。但该 GlobalContent-Article 也与其他 GlobalContent-Groupement 相关,而其他 GlobalContent-Groupement 又与其他内容相关,等等。


GlobalContent Entity


namespace App\Entity;

use App\Entity\User;
use App\Entity\MiniSite;
use Doctrine\ORM\Mapping as ORM;
use PhpParser\Node\Expr\Cast\Bool_;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;

#[ORM\Entity(repositoryClass: 'App\Repository\GlobalContentRepository')]
#[ORM\HasLifecycleCallbacks]
class GlobalContent
{
    use Timestamps;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    #[Groups(['read:contents'])]
    private $id;

    #[ORM\Column(type: 'string', length: 255)]
    #[Groups(['read:contents'])]
    private $main_titre;

    #[ORM\Column(type: 'datetime', nullable: true)]
    #[Groups(['read:contents'])]
    private $date_publication;

    #[ORM\Column(type: 'datetime', nullable: true)]
    #[Groups(['read:contents'])]
    private $date_maj;

    #[ORM\OneToOne(targetEntity: 'App\Entity\Article', mappedBy: 'global_content', cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(nullable: true)]
    #[Groups(['read:contents'])]
    private $article;

    #[ORM\OneToOne(targetEntity: 'App\Entity\Diaporama', mappedBy: 'global_content', cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(nullable: true)]
    #[Groups(['read:contents'])]
    private $diaporama;

    #[ORM\OneToOne(targetEntity: 'App\Entity\Groupement', mappedBy: 'global_content', cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(nullable: true)]
    #[Groups(['read:contents'])]
    #[MaxDepth(1)]
    private $groupement;

    #[ORM\JoinTable(name: 'global_content_groupement')]
    #[ORM\ManyToMany(targetEntity: 'App\Entity\Groupement', inversedBy: 'all_global_contents')]
    #[Groups(['read:contents'])]
    #[MaxDepth(1)]
    private $all_groupements;

    #[ORM\ManyToOne(targetEntity: Theme::class)]
    #[ORM\JoinColumn(nullable: true)]
    #[Groups(['read:contents'])]
    private $mainTheme;

    #[ORM\ManyToOne(targetEntity: 'App\Entity\Image')]
    #[Groups(['read:contents'])]
    private $image;

    #[ORM\Column(type: 'json', nullable: true)]
    private $published = null;



    public function __construct()
    {
        $this->all_groupements = new ArrayCollection();
    }

    public function __toString()
    {
        return $this->main_titre;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getMainTitre(): ?string
    {
        return $this->main_titre;
    }

    public function setMainTitre(string $main_titre): self
    {
        $this->main_titre = $main_titre;

        return $this;
    }

    public function getDatePublication(): ?\DateTimeInterface
    {
        return $this->date_publication;
    }

    public function setDatePublication(\DateTimeInterface $date_publication): self
    {
        $this->date_publication = $date_publication;

        return $this;
    }

    public function getDateMaj(): ?\DateTimeInterface
    {
        return $this->date_maj;
    }

    public function setDateMaj(?\DateTimeInterface $date_maj): self
    {
        $this->date_maj = $date_maj;

        return $this;
    }

    public function getArticle(): ?Article
    {
        return $this->article;
    }

    public function setArticle(?Article $article): self
    {
        $this->article = $article;

        // set the owning side of the relation if necessary
        if ($article->getGlobalContent() !== $this) {
            $article->setGlobalContent($this);
        }

        return $this;
    }

    public function getDiaporama(): ?Diaporama
    {
        return $this->diaporama;
    }

    public function setDiaporama(?Diaporama $diaporama): self
    {
        $this->diaporama = $diaporama;

        return $this;
    }

    public function getGroupement(): ?Groupement
    {
        return $this->groupement;
    }

    public function setGroupement(?Groupement $groupement): self
    {
        $this->groupement = $groupement;

        return $this;
    }

    /**
     * @return Collection|Groupement[]
     */
    public function getAllGroupements(): Collection
    {
        return $this->all_groupements;
    }

    public function addAllGroupement(Groupement $groupement): self
    {
        // $groupement->addGlobalContent($this); // synchronously updating inverse side
        if (!$this->all_groupements->contains($groupement)) {
            $this->all_groupements[] = $groupement;
        }

        return $this;
    }

    public function removeAllGroupement(Groupement $groupement): self
    {
        if ($this->all_groupements->contains($groupement)) {
            $this->all_groupements->removeElement($groupement);
        }

        return $this;
    }

    public function getMainTheme(): ?Theme
    {
        return $this->mainTheme;
    }

    public function setMainTheme(?Theme $mainTheme): self
    {
        $this->mainTheme = $mainTheme;

        return $this;
    }

    public function getImage(): ?Image
    {
        return $this->image;
    }

    public function setImage(?Image $image): self
    {
        $this->image = $image;

        return $this;
    }

    public function getPublished(): ?array
    {
        return $this->published;
    }

    public function setPublished(?array $published): self
    {
        $this->published = $published;

        return $this;
    }

    public function getType(): string
    {
        if(!is_null($this->getArticle())){
            $type = "Article";
        } else if(!is_null($this->getDiaporama())){
            $type = "Diaporama";            
        } else if(!is_null($this->getGroupement())){
            $type = "Groupement";
        } else{
            $type = "Inconnu";            
        }

        return $type;
    }

    public function getSlugType(): string
    {
        if($this->getType() == "Article"){
            $slug = "article";
        } else if($this->getType() == "Diaporama"){
            $slug = "diaporama";
        } else if($this->getType() == "Groupement"){
            $slug = "groupement";
        } else{
            $slug = "inconnu";
        }

        return $slug;
    }
}

分组实体

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;

#[ORM\Entity(repositoryClass: 'App\Repository\GroupementRepository')]
class Groupement
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    #[Groups(['read:contents'])]
    private $id;

    #[ORM\Column(type: 'text', nullable: true)]
    #[Groups(['read:contents'])]
    private $texte;

    #[ORM\OneToOne(targetEntity: 'App\Entity\GlobalContent', inversedBy: 'groupement', cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(nullable: false)]
    #[Groups(['read:contents'])]
    #[MaxDepth(1)]
    private $global_content;

    #[ORM\ManyToMany(targetEntity: 'App\Entity\GlobalContent', mappedBy: 'all_groupements')]
    #[Groups(['read:contents'])]
    #[MaxDepth(1)]
    private $all_global_contents;



    public function __construct()
    {
        $this->all_global_contents = new ArrayCollection();
    }

    public function __toString(): string
    {
        return $this->global_content;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTexte(): ?string
    {
        return $this->texte;
    }

    public function setTexte(?string $texte): self
    {
        $this->texte = $texte;

        return $this;
    }

    public function getGlobalContent(): ?GlobalContent
    {
        return $this->global_content;
    }

    public function setGlobalContent(GlobalContent $global_content): self
    {
        $this->global_content = $global_content;

        return $this;
    }

    /**
     * @return Collection|GlobalContent[]
     */
    public function getAllGlobalContents(): Collection
    {
        return $this->all_global_contents;
    }

    public function addAllGlobalContent(GlobalContent $global_content): self
    {
        // $global_content->addGroupement($this); // synchronously updating inverse side
        if (!$this->all_global_contents->contains($global_content)) {
            $this->all_global_contents[] = $global_content;
        }
        return $this;
    }

    public function removeAllGlobalContent(GlobalContent $global_content): self
    {
        if ($this->all_global_contents->contains($global_content)) {
            $this->all_global_contents->removeElement($global_content);
        }
        return $this;
    }


}

文章实体

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;

#[ORM\Entity(repositoryClass: 'App\Repository\ArticleRepository')]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    #[Groups(['read:contents'])]
    private $id;

    #[ORM\Column(type: 'text', nullable: true)]
    #[Groups(['read:contents'])]
    private $main_content;

    #[ORM\OneToOne(targetEntity: 'App\Entity\GlobalContent', inversedBy: 'article', cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(nullable: false)]
    #[MaxDepth(1)]
    private $global_content;




    public function getId(): ?int
    {
        return $this->id;
    }

    public function getMainContent(): ?string
    {
        return $this->main_content;
    }

    public function setMainContent(?string $main_content): self
    {
        $this->main_content = $main_content;

        return $this;
    }

    public function getGlobalContent(): ?GlobalContent
    {
        return $this->global_content;
    }

    public function setGlobalContent(GlobalContent $global_content): self
    {
        $this->global_content = $global_content;

        return $this;
    }


}

我尝试在实体的每个 GlobalContent 和 Groupement 声明上设置 MaxDepth (并设置 AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true。但我没有看到任何区别。

我还尝试设置 AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {return $object->__toString();} 但这样做只会造成内存泄漏,并产生错误,即使我已将 PHP 的内存限制设置为 2048M。

(我也尝试调用 getId() 而不是 __toString() 宽度相同的结果)

错误:允许的内存大小 2516582400 字节已耗尽(尝试分配 275584840 字节)

这是我调用的代码:

$defaultContext = [
    AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
        return $object->__toString();
    },
    AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
    'groups' => 'read:contents',
];

$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer]);

$contentJSON = $serializer->normalize($global_content, 'json', $defaultContext);

MaxDepth 很有前途,因为我不需要 JSON 中的整个内容。但我没成功。

php symfony doctrine-orm doctrine symfony-serializer
1个回答
0
投票

没关系... 该错误来自于同时使用 GlobalContent.allGroupements 和 Groupement.AllGlobalContents 的事实。我只是把它们分成两个不同的组。

在 GlobalContent 实体中

    #[ORM\JoinTable(name: 'global_content_groupement')]
    #[ORM\ManyToMany(targetEntity: 'App\Entity\Groupement', inversedBy: 'all_global_contents')]
    #[Groups(['read:not-groupements'])]
    #[MaxDepth(1)]
    private $all_groupements;

在分组实体中

    #[ORM\ManyToMany(targetEntity: 'App\Entity\GlobalContent', mappedBy: 'all_groupements')]
    #[Groups(['read:groupements'])]
    #[MaxDepth(1)]
    private $all_global_contents;

在控制器中

    $groups = ['read:contents'];
    if($global_content->getType() == 'Groupement'){
        array_push($groups, 'read:groupements');
    } else{
        array_push($groups, 'read:not-groupements');
    }

    $defaultContext = [
        AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
            return $object->__toString();
        },
        AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
        AbstractNormalizer::CALLBACKS => [
            'date_publication' => $dateCallback,
            'date_maj' => $dateCallback,
        ],
        'groups' => $groups,
    ];

    $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
    $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext);
    $serializer = new Serializer([$normalizer]);


    $contentJSON = $serializer->normalize($global_content, 'json', $defaultContext);    
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.