You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JMS Serializer交叉引用问题:过滤自关联实体的revision类型页面

解决展开children时排除type=revision的Page实体问题

看起来你是在Symfony项目里用LexikFormFilterBundle做过滤,但现在遇到了展开关联的children集合时,里面混进了type为revision的Page,想把这些排除掉对吧?我给你几个可行的方案,你可以根据自己的业务场景选择:

方案1:修改Doctrine关联映射(一劳永逸)

如果你的业务逻辑里,children集合永远不应该包含revision类型的Page,那直接在实体的关联注解里加条件是最简单的:

// src/Entity/Page.php
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */
class Page
{
    // ...其他属性和方法

    /**
     * @ORM\OneToMany(targetEntity="Page", mappedBy="parent")
     * @ORM\Where(clause="type != 'revision'")
     */
    private $children;

    // ...
}

这样Doctrine在加载children集合的时候,会自动过滤掉type=revision的记录,不管是通过API展开还是其他地方调用,都不会拿到这些数据。不过要注意,如果之后有场景需要获取包含revision的子页面,你得再定义一个新的关联(比如allChildren),不带这个WHERE条件。

方案2:序列化阶段过滤(不影响原始数据)

如果只是在API响应里需要过滤,不想修改Doctrine的默认加载行为,可以用序列化器的事件来处理。比如用Symfony自带的Serializer:

先写一个序列化订阅器:

// src/Serializer/PageSerializerSubscriber.php
use Symfony\Component\Serializer\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Serializer\EventDispatcher\PreSerializeEvent;
use Doctrine\Common\Collections\ArrayCollection;
use App\Entity\Page;
use Symfony\Component\HttpFoundation\RequestStack;

class PageSerializerSubscriber implements EventSubscriberInterface
{
    private $requestStack;

    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }

    public static function getSubscribedEvents()
    {
        return [
            ['event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize', 'class' => Page::class],
        ];
    }

    public function onPreSerialize(PreSerializeEvent $event)
    {
        /** @var Page $page */
        $page = $event->getObject();
        $request = $this->requestStack->getCurrentRequest();

        // 只在请求需要展开children的时候才过滤
        if ($request && $request->query->has('expand') && strpos($request->query->get('expand'), 'page_children') !== false) {
            $filteredChildren = array_filter(
                $page->getChildren()->toArray(),
                fn($child) => $child->getType() !== 'revision'
            );
            
            $page->setChildren(new ArrayCollection($filteredChildren));
        }
    }
}

然后在services.yaml里注册这个订阅器:

services:
    App\Serializer\PageSerializerSubscriber:
        tags:
            - { name: serializer.event_subscriber }

这个方法的好处是不会影响Doctrine加载的原始数据,只是在序列化输出的时候过滤掉不需要的元素,灵活性更高。

方案3:结合LexikFormFilterBundle在查询阶段过滤

如果你的API已经在用LexikFormFilterBundle做列表过滤,也可以在FilterType里针对children关联添加过滤条件,确保查询时只加载符合要求的子页面:

// src/Form/Filter/PageFilterType.php
use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class PageFilterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // 其他过滤字段...
            ->add('children', Filters\EntityFilterType::class, [
                'class' => Page::class,
                'filter_fields' => [
                    'type' => [
                        'operator' => 'neq',
                        'value' => 'revision'
                    ],
                ],
            ]);
    }

    public function getParent()
    {
        return Filters\FilterType::class;
    }
}

然后在处理查询的时候,确保这个过滤条件被应用。核心思路就是在QueryBuilder里给children关联加上type != 'revision'的条件,和Lexik的过滤逻辑结合起来。

方案4:手动修改QueryBuilder(灵活可控)

如果以上方法都不符合你的需求,还可以在Repository或者控制器里手动修改查询构建器,当检测到expand=page_children参数时,添加过滤条件:

// src/Repository/PageRepository.php
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpFoundation\RequestStack;

class PageRepository extends EntityRepository
{
    private $requestStack;

    public function __construct($em, $class, RequestStack $requestStack)
    {
        parent::__construct($em, $class);
        $this->requestStack = $requestStack;
    }

    public function getFilteredPagesQueryBuilder()
    {
        $qb = $this->createQueryBuilder('p');
        $request = $this->requestStack->getCurrentRequest();

        // 检查是否需要展开children并过滤
        if ($request && $request->query->has('expand') && strpos($request->query->get('expand'), 'page_children') !== false) {
            $qb->leftJoin('p.children', 'c')
               ->andWhere('c.type != :revision')
               ->setParameter('revision', 'revision')
               ->addSelect('c');
        }

        return $qb;
    }
}

这样在查询页面列表的时候,只要请求带了expand=page_children,就会自动过滤掉子页面中的revision类型。


内容的提问来源于stack exchange,提问作者Łukasz D. Tulikowski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:52:07