如何在API Platform中统一将自定义Url对象类型设为字符串?
问题
项目中使用自定义Doctrine类型处理URL,实体中的URL字符串会返回App\Util\Url对象,已实现归一化器/反归一化器,旧API中该类型以字符串形式传输。
接入API Platform后,OpenAPI文档将Url字段描述为对象:
Url.jsonld{ @context {...} @id [...] @type [...] schema [...] domain [...] path [...] }
但实际API响应中Url字段是正常的字符串格式:
{ "@context": "/api/contexts/Company", "@id": "/api/companies", "@type": "hydra:Collection", "hydra:member": [ { "@id": "/api/companies/1", "@type": "Company", "name": "company name", "description": "company description", "id": 1, "url": "http://test.com" } ], "hydra:totalItems": 1 }
虽然可以通过#[ApiProperty(openapiContext: [])]单独配置每个字段,但项目中Url字段数量较多,不想逐个配置。希望实现像DateTime对象那样,让API Platform统一将所有Url对象在OpenAPI文档中显示为字符串类型。
解决方案
可以通过以下几种方式实现全局统一处理App\Util\Url类型的OpenAPI描述:
方法一:自定义OpenAPI类型映射扩展
创建一个装饰器类,修改API Platform生成的OpenAPI文档,将所有指向Url类型的属性替换为字符串类型:
<?php namespace App\OpenApi; use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface; use ApiPlatform\Core\OpenApi\Model\Schema; use ApiPlatform\Core\OpenApi\OpenApi; class UrlTypeOpenApiExtension implements OpenApiFactoryInterface { private $decorated; public function __construct(OpenApiFactoryInterface $decorated) { $this->decorated = $decorated; } public function __invoke(array $context = []): OpenApi { $openApi = $this->decorated->__invoke($context); // 遍历所有Schema,替换Url类型的属性定义 foreach ($openApi->getComponents()->getSchemas() as $schema) { if (!$schema->getProperties()) { continue; } foreach ($schema->getProperties() as $propertyName => $propertySchema) { if ($propertySchema->getRef() === '#/components/schemas/Url') { // 替换为URI格式的字符串类型 $newSchema = new Schema(type: 'string', format: 'uri'); $schema->setProperty($propertyName, $newSchema); } } } // 移除自动生成的Url Schema定义 $openApi->getComponents()->getSchemas()->offsetUnset('Url'); return $openApi; } }
在services.yaml中注册该服务:
services: App\OpenApi\UrlTypeOpenApiExtension: decorates: 'api_platform.open_api.factory' arguments: ['@.inner']
方法二:通过元数据扩展全局设置属性配置
实现属性元数据工厂的装饰器,为所有App\Util\Url类型的属性自动添加openapiContext配置:
<?php namespace App\Metadata; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\PropertyMetadata; use App\Util\Url; use Symfony\Component\PropertyInfo\Type; class UrlPropertyMetadataFactory implements PropertyMetadataFactoryInterface { private $decorated; public function __construct(PropertyMetadataFactoryInterface $decorated) { $this->decorated = $decorated; } public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata { $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); $type = $propertyMetadata->getType(); if ($type instanceof Type && $type->getClassName() === Url::class) { // 为Url类型属性设置统一的OpenAPI描述 $propertyMetadata = $propertyMetadata->withOpenapiContext([ 'type' => 'string', 'format' => 'uri' ]); } return $propertyMetadata; } }
注册服务:
services: App\Metadata\UrlPropertyMetadataFactory: decorates: 'api_platform.metadata.property.metadata_factory' arguments: ['@.inner']
内容的提问来源于stack exchange,提问作者marv255
相关产品推荐
相关产品推荐

