JMS 序列化程序 - 交叉引用问题
JMS Serializer - Cross reference issue
我的实体有两个自引用 OneToMany
关系 children
和 revisions
.
<?php
namespace App\Entity\CMS;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* @ORM\Entity
* @JMS\ExclusionPolicy("ALL")
*/
class Page
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue(strategy="IDENTITY")
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @var Page[]|ArrayCollection
*
* @ORM\OneToMany(targetEntity="CoreBundle\Entity\CMS\Page", mappedBy="parent")
*
* @JMS\Groups({"page_children"})
* @JMS\Expose()
*/
protected $children;
/**
* @var Page[]|ArrayCollection
*
* @ORM\OneToMany(targetEntity="CoreBundle\Entity\CMS\Page", mappedBy="page")
*
* @JMS\Groups({"revisions"})
* @JMS\Expose()
*
*/
protected $revisions;
/**
* @var string
*
* @ORM\Column(type="string", value={"main", "revision"})
*
* @JMS\Expose()
*
*/
protected $type;
#...
}
我公开了两个集合 - children
和 revisions
。另外 type
被暴露 - 它是 Page
是否属于 revisions
的指标。
请求 {{host}}/api/pages?expand=page_children
returns 结果,其中包括 Pages
两种类型。
{
"id": "1",
"type": "main",
"children": [
{
"id": "3",
"type": "main",
"children": [
{
"id": "5",
"type": "main",
"children": []
},
{
"id": "6",
"type": "revision",
"children": []
}
],
},
{
"id": "4,
"type": "revision",
"children": []
}
],
"id": "2',
"type": "revision",
"children": []
}
我想从响应 Pages
中排除 revision
类型。所以我的最终结果是这样的:
{
"id": "1",
"type": "main",
"children": [
{
"id": "3",
"type": "main",
"children": [
{
"id": "5",
"type": "main",
"children": []
}
]
}
]
}
通常,我使用 LexikFormFilterBundle 来过滤结果。
但是在这种情况下,组合请求如下:
{{host}}/api/expand=page_children&page_filter[type]=main
仅适用于第一级结果。
我考虑过Dynamic Exclusion Strategy or Subscribing Handler。不幸的是,我无法找出解决方案。
您可以通过 class 中的自定义方法并告诉 JMS 使用它。
/**
* @VirtualProperty
* @SerializedName("my_collection")
*/
public function getMyCollection()
{
return $this->collection->filterByType.....
}
如果您想使其动态化,您可以在虚拟 属性 中存储一些数据并在此方法中使用它。
要控制深度,请使用 @JMS\MaxDepth(depth=1)
.
但从概念上讲,这似乎是一个非常糟糕的主意。我宁愿审查范围,也可能以适当的方式拆分。
我通过实施 Subscribing Handler
解决了这个问题
<?php
namespace CoreBundle\Serializer\Subscriber\CMS;
use App\Entity\CMS\Page;
use JMS\Serializer\EventDispatcher\Events;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
class PageSubscriber extends SubscribingHandlerInterface
{
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
[
'event' => Events::PRE_SERIALIZE,
'method' => 'onPreSerialize',
'class' => Page::class,
'format' => 'json',
],
];
}
/**
* @param PreSerializeEvent $event
*/
public function onPreSerialize(PreSerializeEvent $event)
{
$entity = $event->getObject();
if (!$entity instanceof Page) {
return;
}
if ($this->isSerialisingForGroup($event, 'exclude_revisions')) {
$this->excludeRevisions($entity);
}
}
/**
* @param Page $page
*/
private function excludeRevisions(Page $page): void
{
foreach ($page->getChildren() as $child) {
if ($child->getStatus() === 'revision') {
$page->removeChild($child);
}
}
}
}
缺点:此时所有数据都被抓取。元素 "type": "revision"
将包含在非第一级中,它会展开,这可能导致 Allowed memory size exhausted
.
另一种可能的方法是使用 Doctrine postLoad
事件。
<?php
namespace App\Service\Doctrine;
use App\Entity\CMS\Page;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use RuntimeException;
class PageListener implements EventSubscriber
{
/** @var bool $canFlush */
private $canFlush = true;
/**
* {@inheritdoc}
*/
public function getSubscribedEvents()
{
return [
Events::postLoad,
Events::onFlush,
];
}
/**
* @param Page $page
*/
public function onPostLoad(Page $page): void
{
$children = $page->getChildren();
foreach ($children as $child) {
if ($child->getStatus === 'revision') {
$page->removeChild($child);
}
}
}
/**
* @param OnFlushEventArgs $eventArgs
*/
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityDeletions() as $entityDeletion) {
if ($entityDeletion instanceof Page){
throw new RuntimeException('Flushing Page at this point will remove all Revisions.');
}
}
}
}
缺点:可能很危险,并且会缩小未来可能的变化范围。
我的实体有两个自引用 OneToMany
关系 children
和 revisions
.
<?php
namespace App\Entity\CMS;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* @ORM\Entity
* @JMS\ExclusionPolicy("ALL")
*/
class Page
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue(strategy="IDENTITY")
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @var Page[]|ArrayCollection
*
* @ORM\OneToMany(targetEntity="CoreBundle\Entity\CMS\Page", mappedBy="parent")
*
* @JMS\Groups({"page_children"})
* @JMS\Expose()
*/
protected $children;
/**
* @var Page[]|ArrayCollection
*
* @ORM\OneToMany(targetEntity="CoreBundle\Entity\CMS\Page", mappedBy="page")
*
* @JMS\Groups({"revisions"})
* @JMS\Expose()
*
*/
protected $revisions;
/**
* @var string
*
* @ORM\Column(type="string", value={"main", "revision"})
*
* @JMS\Expose()
*
*/
protected $type;
#...
}
我公开了两个集合 - children
和 revisions
。另外 type
被暴露 - 它是 Page
是否属于 revisions
的指标。
请求 {{host}}/api/pages?expand=page_children
returns 结果,其中包括 Pages
两种类型。
{
"id": "1",
"type": "main",
"children": [
{
"id": "3",
"type": "main",
"children": [
{
"id": "5",
"type": "main",
"children": []
},
{
"id": "6",
"type": "revision",
"children": []
}
],
},
{
"id": "4,
"type": "revision",
"children": []
}
],
"id": "2',
"type": "revision",
"children": []
}
我想从响应 Pages
中排除 revision
类型。所以我的最终结果是这样的:
{
"id": "1",
"type": "main",
"children": [
{
"id": "3",
"type": "main",
"children": [
{
"id": "5",
"type": "main",
"children": []
}
]
}
]
}
通常,我使用 LexikFormFilterBundle 来过滤结果。 但是在这种情况下,组合请求如下:
{{host}}/api/expand=page_children&page_filter[type]=main
仅适用于第一级结果。
我考虑过Dynamic Exclusion Strategy or Subscribing Handler。不幸的是,我无法找出解决方案。
您可以通过 class 中的自定义方法并告诉 JMS 使用它。
/**
* @VirtualProperty
* @SerializedName("my_collection")
*/
public function getMyCollection()
{
return $this->collection->filterByType.....
}
如果您想使其动态化,您可以在虚拟 属性 中存储一些数据并在此方法中使用它。
要控制深度,请使用 @JMS\MaxDepth(depth=1)
.
但从概念上讲,这似乎是一个非常糟糕的主意。我宁愿审查范围,也可能以适当的方式拆分。
我通过实施 Subscribing Handler
解决了这个问题<?php
namespace CoreBundle\Serializer\Subscriber\CMS;
use App\Entity\CMS\Page;
use JMS\Serializer\EventDispatcher\Events;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
class PageSubscriber extends SubscribingHandlerInterface
{
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
[
'event' => Events::PRE_SERIALIZE,
'method' => 'onPreSerialize',
'class' => Page::class,
'format' => 'json',
],
];
}
/**
* @param PreSerializeEvent $event
*/
public function onPreSerialize(PreSerializeEvent $event)
{
$entity = $event->getObject();
if (!$entity instanceof Page) {
return;
}
if ($this->isSerialisingForGroup($event, 'exclude_revisions')) {
$this->excludeRevisions($entity);
}
}
/**
* @param Page $page
*/
private function excludeRevisions(Page $page): void
{
foreach ($page->getChildren() as $child) {
if ($child->getStatus() === 'revision') {
$page->removeChild($child);
}
}
}
}
缺点:此时所有数据都被抓取。元素 "type": "revision"
将包含在非第一级中,它会展开,这可能导致 Allowed memory size exhausted
.
另一种可能的方法是使用 Doctrine postLoad
事件。
<?php
namespace App\Service\Doctrine;
use App\Entity\CMS\Page;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use RuntimeException;
class PageListener implements EventSubscriber
{
/** @var bool $canFlush */
private $canFlush = true;
/**
* {@inheritdoc}
*/
public function getSubscribedEvents()
{
return [
Events::postLoad,
Events::onFlush,
];
}
/**
* @param Page $page
*/
public function onPostLoad(Page $page): void
{
$children = $page->getChildren();
foreach ($children as $child) {
if ($child->getStatus === 'revision') {
$page->removeChild($child);
}
}
}
/**
* @param OnFlushEventArgs $eventArgs
*/
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityDeletions() as $entityDeletion) {
if ($entityDeletion instanceof Page){
throw new RuntimeException('Flushing Page at this point will remove all Revisions.');
}
}
}
}
缺点:可能很危险,并且会缩小未来可能的变化范围。