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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:09:30