I have a project with a custom doctrine type for URL. It returns an App\Util\Url
object for each URL string in each entity. I also have a normalizer/denormalizer for it, so I get used to transfer it as string in old API.
Now I would like to use API platform. It creates a OpenAPI description for Url
like
Url.jsonld{
@context {...}
@id [...]
@type [...]
schema [...]
domain [...]
path [...]
}
I still see a string in the API platform response. It means that normalizer works fine, but OpenAPI still shows my field not as a string, but as an object.
{
"@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
}
I can use a special param to replace default OpenAPI annotation
#[ApiProperty(openapiContext: [])]
Unfortunately, there is a lot of Url
fields and don't want to create openapiContext
for each. Is there a way to explain API platform that all Url
objects should be processed as strings as it happens with DateTime
objects?
CodePudding user response:
The best workaround I found is to decorate api_platform.metadata.property.metadata_factory
.
Create decorator like
namespace App\ApiPlatform;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use App\Util\Url;
use Symfony\Component\PropertyInfo\Type;
class PropertyMetadataFactoryDecorator implements PropertyMetadataFactoryInterface
{
private readonly PropertyMetadataFactoryInterface $decorated;
public function __construct(PropertyMetadataFactoryInterface $decorated)
{
$this->decorated = $decorated;
}
public function create(string $resourceClass, string $property, array $options = []): ApiProperty
{
$result = $this->decorated->create($resourceClass, $property, $options);
if ($this->isUrlObject($result)) {
$result = $result->withBuiltinTypes(
[
new Type(Type::BUILTIN_TYPE_STRING, !$result->isRequired()),
]
);
}
return $result;
}
private function isUrlObject(ApiProperty $property): bool
{
$res = false;
foreach ($property->getBuiltinTypes() as $type) {
if ($type->getClassName() === Url::class) {
$res = true;
break;
}
}
return $res;
}
}
Add decorator to config/services.yaml
App\ApiPlatform\PropertyMetadataFactoryDecorator:
decorates: 'api_platform.metadata.property.metadata_factory'
arguments: ['@.inner']
decoration_priority: -100
Not sure how will it affects other parts of the app, but for now works fine.