JMS Serializer - 交叉引用问题

时间:2018-02-12 18:51:48

标签: symfony annotations jmsserializerbundle

我的实体有两个自引用OneToMany关系childrenrevisions

<?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;

    #...
}

我正在展示两个集合 - childrenrevisions。此外,type已展开 - 如果Page属于revisions,则表示该指标。

请求{{host}}/api/pages?expand=page_children会返回包含两种类型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 StrategySubscribing Handler。不幸的是,我无法弄清楚解决方案。

2 个答案:

答案 0 :(得分:2)

我解决了实施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.');
            }
        }
    }
}

缺点:可能是危险的,也可能缩小未来可能的变化。

答案 1 :(得分:0)

您可以通过类中的自定义方法进行操作,并告诉JMS使用它。

/** * @VirtualProperty * @SerializedName("my_collection") */ public function getMyCollection() { return $this->collection->filterByType..... }

如果要使其动态化,可以将一些数据存储在虚拟属性中,并在此方法中使用它。 要控制深度,请使用@JMS\MaxDepth(depth=1)

但从概念上讲,这似乎是一个非常糟糕的主意。我宁愿审查范围,也许会以适当的方式分解。