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

Symfony+API Platform中子资源IsEnabled过滤失效问题求助

问题描述

通过API Platform的properties[]机制向Vue.js应用获取数据,请求URL格式为http://address/resource?properties[subresources]=id。自定义了Doctrine扩展用于校验实体的isEnabled字段,该扩展会检查实体是否实现IsEnabledAwareInterface接口,但子资源的isEnabled过滤不生效。尝试过编写订阅器、监听器或自定义归一化器,但均未成功,因为请求仅序列化一次。希望获得解决方案,尽量不想重写Vue应用。

现有Doctrine扩展代码
<?php

namespace App\Doctrine;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use App\Entity\Users\Admin;
use App\Entity\Users\AdminWorker;
use App\Entity\Interfaces\IsEnabledAwareInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;

final class IsEnabledAwareExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function applyToCollection(
        QueryBuilder $queryBuilder,
        QueryNameGeneratorInterface $queryNameGenerator,
        string $resourceClass,
        string $operationName = null
    ) {
        $this->addWhere($queryBuilder, $resourceClass);
    }

    public function applyToItem(
        QueryBuilder $queryBuilder,
        QueryNameGeneratorInterface $queryNameGenerator,
        string $resourceClass,
        array $identifiers,
        string $operationName = null,
        array $context = []
    ) {
        $this->addWhere($queryBuilder, $resourceClass);
    }

    private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
    {
        $class = new \ReflectionClass($resourceClass);

        // skip if entity has not isEnabled field
        if (false === $class->implementsInterface(IsEnabledAwareInterface::class)) {
            return;
        }
        // skip for admin users (they should see not enabled also)
        $user = $this->security->getUser();
        if ($user instanceof Admin || $user instanceof AdminWorker) {
            return;
        }

        $rootAlias = $queryBuilder->getRootAliases()[0];
        $queryBuilder->andWhere(sprintf('%s.isEnabled = true', $rootAlias));
    }
}
解决方案

问题根源在于当前的Doctrine扩展仅作用于主资源的查询,子资源如果通过延迟加载或单独查询加载时,扩展不会自动应用过滤规则。以下是两种可行的解决方案:

方案一:使用Doctrine全局实体过滤器(推荐)

Doctrine的全局过滤器可以自动对所有实现IsEnabledAwareInterface的实体添加isEnabled = true的条件,覆盖主查询、子查询、关联加载等所有场景,无需修改Vue应用。

步骤1:创建全局过滤器类

<?php

namespace App\Doctrine\Filter;

use App\Entity\Interfaces\IsEnabledAwareInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Symfony\Component\Security\Core\Security;
use App\Entity\Users\Admin;
use App\Entity\Users\AdminWorker;

class IsEnabledFilter extends SQLFilter
{
    private $security;

    public function setSecurity(Security $security): void
    {
        $this->security = $security;
    }

    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
    {
        // 跳过未实现接口的实体
        if (!$targetEntity->reflClass->implementsInterface(IsEnabledAwareInterface::class)) {
            return '';
        }

        // 管理员跳过过滤
        $user = $this->security->getUser();
        if ($user instanceof Admin || $user instanceof AdminWorker) {
            return '';
        }

        // 添加isEnabled过滤条件
        return sprintf('%s.isEnabled = 1', $targetTableAlias);
    }
}

步骤2:配置Doctrine启用过滤器

在config/packages/doctrine.yaml中添加过滤器配置:

doctrine:
    orm:
        filters:
            is_enabled_filter:
                class: App\Doctrine\Filter\IsEnabledFilter
                enabled: true

步骤3:注入Security依赖

在config/services.yaml中注册过滤器并注入Security服务:

services:
    App\Doctrine\Filter\IsEnabledFilter:
        calls:
            - [setSecurity, ['@security.helper']]
        tags:
            - { name: doctrine.orm.filter }

方案二:修改现有Doctrine扩展,处理关联查询

如果不想使用全局过滤器,可以修改现有扩展,在主查询中对关联的子资源主动添加过滤条件(仅适用于API Platform通过Fetch Join加载子资源的场景)。

修改IsEnabledAwareExtension的addWhere方法:

private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
    $class = new \ReflectionClass($resourceClass);

    if (false === $class->implementsInterface(IsEnabledAwareInterface::class)) {
        return;
    }

    $user = $this->security->getUser();
    if ($user instanceof Admin || $user instanceof AdminWorker) {
        return;
    }

    $rootAlias = $queryBuilder->getRootAliases()[0];
    $queryBuilder->andWhere(sprintf('%s.isEnabled = true', $rootAlias));

    // 遍历主实体的所有关联,对实现接口的关联实体添加过滤
    $em = $queryBuilder->getEntityManager();
    $metadata = $em->getClassMetadata($resourceClass);
    foreach ($metadata->getAssociationMappings() as $assocName => $assocMapping) {
        $targetClass = $assocMapping['targetEntity'];
        $targetRefl = new \ReflectionClass($targetClass);
        if ($targetRefl->implementsInterface(IsEnabledAwareInterface::class)) {
            $assocAlias = $rootAlias . '_' . $assocName;
            // 若未join关联则主动join
            if (!$queryBuilder->getDQLPart('join') || !isset($queryBuilder->getDQLPart('join')[$rootAlias][$assocName])) {
                $queryBuilder->leftJoin(sprintf('%s.%s', $rootAlias, $assocName), $assocAlias);
            }
            // 添加关联实体的isEnabled过滤
            $queryBuilder->andWhere(sprintf('%s.isEnabled = true', $assocAlias));
        }
    }
}

内容的提问来源于stack exchange,提问作者Adrian Wolf

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 23:12:26