如何在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参数并加入路由参数。
步骤:
- 创建自定义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); } } }
- 在
services.yaml中替换原服务:
# config/services.yaml services: ApiPlatform\Core\Symfony\Routing\IriConverterInterface: alias: App\ApiPlatform\IriConverter
解决方案2:通过序列化上下文传递var
如果不想重写整个IriConverter,可以在序列化前把var注入上下文,再修改ItemNormalizer来生成带var的IRI。
步骤:
- 写事件订阅器,把请求中的
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); } }
- 自定义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); } }
- 在
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调用。
步骤:
- 修改路由前缀,添加默认值:
# config/routes/api_platform.yaml api_platform: resource: . type: api_platform prefix: /{var}/api defaults: var: default_db # 默认数据库标识
- 写事件订阅器,把
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
相关产品推荐
相关产品推荐

