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

如何在API Platform 3.1的路由前缀中使用变量?

问题描述

在API Platform 3.1中,我尝试给路由前缀加变量作为数据库选择器:

# config/routes/api_platform.yaml
api_platform:
    resource: .
    type: api_platform
    prefix: /{var}/api

但因为{var}既不在操作元数据里,也不是实体的字段,所有实体操作都报错:

Unable to generate an IRI for the item of type *实体路径*

我的目标是生成路由时自动把var纳入标识符,之前试过修改IriConverter或AbstractItemNormalizer但没成功,不想重写Symfony Router(改动太大),求可行的解决思路。


解决方案1:自定义IriConverter,从请求上下文取var

核心是重写API Platform自带的IriConverter,在生成IRI时自动从当前请求中抓取var参数并加入路由参数。

步骤:

  1. 创建自定义IriConverter类,继承原类:
// src/ApiPlatform/IriConverter.php
namespace App\ApiPlatform;

use ApiPlatform\Core\Api\ResourceClassResolverInterface;
use ApiPlatform\Core\Identifier\IdentifierConverterInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

final class IriConverter extends \ApiPlatform\Core\Symfony\Routing\IriConverter
{
    private RequestStack $requestStack;

    public function __construct(
        UrlGeneratorInterface $urlGenerator,
        ResourceClassResolverInterface $resourceClassResolver,
        ResourceMetadataFactoryInterface $resourceMetadataFactory,
        IdentifierConverterInterface $identifierConverter,
        RequestStack $requestStack,
        array $formats = [],
        $itemNormalizer = null
    ) {
        parent::__construct($urlGenerator, $resourceClassResolver, $resourceMetadataFactory, $identifierConverter, $formats, $itemNormalizer);
        $this->requestStack = $requestStack;
    }

    public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
    {
        $request = $this->requestStack->getCurrentRequest();
        $var = $request?->get('var');

        $resourceClass = $this->resourceClassResolver->getResourceClass($item);
        $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);

        $routeName = $resourceMetadata->getCollectionOperationRouteName('get') ?? $resourceMetadata->getItemOperationRouteName('get');
        if (!$routeName) {
            throw new \RuntimeException(sprintf('No route name found for resource "%s".', $resourceClass));
        }

        $parameters = $this->identifierConverter->convertToIdentifiers($item);
        // 把var参数加入路由参数数组
        if ($var) {
            $parameters['var'] = $var;
        }

        try {
            return $this->urlGenerator->generate($routeName, $parameters, $referenceType);
        } catch (\Exception $e) {
            throw new \ApiPlatform\Core\Exception\InvalidArgumentException(sprintf('Unable to generate an IRI for the item of type "%s".', $resourceClass), 0, $e);
        }
    }
}
  1. 在services.yaml中替换原服务:
# config/services.yaml
services:
    ApiPlatform\Core\Symfony\Routing\IriConverterInterface:
        alias: App\ApiPlatform\IriConverter

解决方案2:通过序列化上下文传递var

如果不想重写整个IriConverter,可以在序列化前把var注入上下文,再修改ItemNormalizer来生成带var的IRI。

步骤:

  1. 写事件订阅器,把请求中的var存入序列化上下文:
// src/EventSubscriber/SerializerContextSubscriber.php
namespace App\EventSubscriber;

use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;

final class SerializerContextSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::VIEW => ['addVarToContext', EventPriorities::PRE_SERIALIZE],
        ];
    }

    public function addVarToContext(ViewEvent $event): void
    {
        $request = $event->getRequest();
        $var = $request->get('var');
        if (!$var) return;

        $context = $event->getRequest()->attributes->get('_api_normalization_context', []);
        $context['var'] = $var;
        $event->getRequest()->attributes->set('_api_normalization_context', $context);
    }
}
  1. 自定义ItemNormalizer,重写IRI生成逻辑:
// src/ApiPlatform/Serializer/ItemNormalizer.php
namespace App\ApiPlatform\Serializer;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;

final class ItemNormalizer extends AbstractItemNormalizer
{
    private IriConverterInterface $iriConverter;

    public function __construct(IriConverterInterface $iriConverter, ...$args)
    {
        parent::__construct(...$args);
        $this->iriConverter = $iriConverter;
    }

    protected function getIriFromItem($item, $format = null, array $context = []): string
    {
        // 如果上下文里有var,直接用自定义IriConverter生成带var的IRI
        if (isset($context['var'])) {
            return $this->iriConverter->getIriFromItem($item);
        }

        return parent::getIriFromItem($item, $format, $context);
    }
}
  1. 在services.yaml中替换原ItemNormalizer:
# config/services.yaml
services:
    ApiPlatform\Core\Serializer\ItemNormalizer:
        class: App\ApiPlatform\Serializer\ItemNormalizer
        arguments:
            - '@App\ApiPlatform\IriConverter'
            - '@api_platform.metadata.resource.metadata_factory'
            - '@api_platform.iri_converter'
            - '@api_platform.serializer.name_converter'
            - '@api_platform.validator'
            - '@api_platform.serializer.abstract_normalizer.property_accessor'
            - '@api_platform.serializer.abstract_normalizer.property_type_extractor'

解决方案3:路由默认值+请求属性传递(适合var有默认值的场景)

如果var有固定的默认值,可以先在路由里设置默认值,再通过事件把var存入请求属性,供IriConverter调用。

步骤:

  1. 修改路由前缀,添加默认值:
# config/routes/api_platform.yaml
api_platform:
    resource: .
    type: api_platform
    prefix: /{var}/api
    defaults:
        var: default_db # 默认数据库标识
  1. 写事件订阅器,把var存入请求属性:
// src/EventSubscriber/VarRequestSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

final class VarRequestSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => ['setVarAttribute'],
        ];
    }

    public function setVarAttribute(RequestEvent $event): void
    {
        $request = $event->getRequest();
        $var = $request->get('var');
        if ($var) {
            $request->attributes->set('db_var', $var);
        }
    }
}

之后在自定义IriConverter里,通过$request->attributes->get('db_var')获取值,加入路由参数即可。


内容的提问来源于stack exchange,提问作者Médéric

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 14:02:52