如何在API Platform的ContextBuilder中添加动态过滤器或分页?
问题
尝试通过ContextBuilder动态强制设置过滤器或分页,例如为public:read分组强制启用分页,编写了如下实现类:
namespace App\Serializer; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use Symfony\Component\HttpFoundation\Request; final class FooContextBuilder implements SerializerContextBuilderInterface { private $decorated; public function __construct(SerializerContextBuilderInterface $decorated) { $this->decorated = $decorated; } public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); if (($resourceClass === Foo::class $context['operation_type'] ?? null) === 'collection' && true === $normalization ) { if ((isset($context['groups']) && in_array('public:read', $context['groups']) ) { $context['filters']['pagination'] = true; } } return $context; } }
对应的services.yml配置:
services: ... App\Serializer\RouteContextBuilder: decorates: 'api_platform.serializer.context_builder' arguments: [ '@App\Serializer\RouteContextBuilder.inner' ] autoconfigure: false
但$context['filters']在ContextBuilder阶段尚未生成,仅在DataProvider等后续环节可用,调整decoration_priority也无法解决问题。请问如何通过上下文动态添加过滤器或分页?是否存在其他可装饰的接口,在归一化流程后期、过滤器应用前被调用?
解决方案
方法一:直接修改Request查询参数
API Platform的分页/过滤器参数是通过Request传递的,你可以在ContextBuilder中直接修改Request的query参数,后续的过滤器逻辑会自动读取这些参数生效:
// 在FooContextBuilder的createFromRequest方法中 public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); $resourceClass = $context['resource_class'] ?? null; if ($resourceClass === Foo::class && ($context['operation_type'] ?? null) === 'collection' && $normalization && isset($context['groups']) && in_array('public:read', $context['groups']) ) { // 强制启用分页 $request->query->set('pagination', 'true'); // 设置默认分页参数(可选) if (!$request->query->has('page')) { $request->query->set('page', 1); } if (!$request->query->has('itemsPerPage')) { $request->query->set('itemsPerPage', 10); } } return $context; }
这种方式不需要操作$context['filters'],直接通过Request传递参数,是最直接的解决方案。
方法二:装饰DataProvider(推荐)
如果需要更底层的控制,可以装饰Doctrine ORM的默认DataProvider,在数据获取前处理分页逻辑:
实现自定义DataProvider
namespace App\DataProvider; use ApiPlatform\Core\Bridge\Doctrine\Orm\DataProvider\CollectionDataProvider; use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use Symfony\Component\HttpFoundation\RequestStack; final class DynamicPaginationDataProvider implements CollectionDataProviderInterface, RestrictedDataProviderInterface { private $decorated; private $requestStack; public function __construct(CollectionDataProviderInterface $decorated, RequestStack $requestStack) { $this->decorated = $decorated; $this->requestStack = $requestStack; } public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return $this->decorated->supports($resourceClass, $operationName, $context); } public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable { $request = $this->requestStack->getCurrentRequest(); if ($resourceClass === Foo::class && ($context['operation_type'] ?? null) === 'collection' && in_array('public:read', $context['groups'] ?? []) ) { // 强制启用分页 $request->query->set('pagination', 'true'); // 或直接修改上下文(部分场景下生效) $context['filters']['pagination'] = true; } return $this->decorated->getCollection($resourceClass, $operationName, $context); } }
配置服务
services: App\DataProvider\DynamicPaginationDataProvider: decorates: 'api_platform.doctrine.orm.default_data_provider' arguments: ['@.inner', '@request_stack'] autoconfigure: true
方法三:使用事件订阅器
通过API Platform的内核事件,在过滤器应用前修改参数:
实现事件订阅器
namespace App\EventSubscriber; use ApiPlatform\Core\EventListener\EventPriorities; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; final class DynamicPaginationSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => ['onKernelRequest', EventPriorities::PRE_READ], ]; } public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); $context = $request->attributes->get('_api_context'); $resourceClass = $request->attributes->get('_api_resource_class'); if (!$context || !$resourceClass) { return; } if ($resourceClass === Foo::class && ($context['operation_type'] ?? null) === 'collection' && in_array('public:read', $context['groups'] ?? []) ) { $request->query->set('pagination', 'true'); // 设置默认分页参数 $request->query->setDefault('page', 1); $request->query->setDefault('itemsPerPage', 10); } } }
这个订阅器会在PRE_READ阶段(数据读取前、过滤器应用前)修改Request参数,确保分页逻辑生效。
内容的提问来源于stack exchange,提问作者DurandA
相关产品推荐
相关产品推荐

