当我想要规范化对象宽度 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 中的整个内容。但我没成功。
没关系... 该错误来自于同时使用 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);