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
相关产品推荐
相关产品推荐

