API Platform version(s) affected: 2.5.4
Description
When I upgrade api-platform/core 2.5.3 to 2.5.4 I have the following problem on api docs: "There is no PropertyInfo extractor supporting the class "DateInterval"."
How to reproduce
I think we just have to put a property with a DateInterval in an exposed entity and it will reproduce the problem.
Possible Solution
No idea
Additional Context
ApiPlatform\Core\Exception\RuntimeException:
There is no PropertyInfo extractor supporting the class "DateInterval".
at vendor/api-platform/core/src/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyNameCollectionFactory.php:46
at ApiPlatform\Core\Bridge\Symfony\PropertyInfo\Metadata\Property\PropertyInfoPropertyNameCollectionFactory->create('DateInterval', array())
(vendor/api-platform/core/src/Metadata/Property/Factory/InheritedPropertyNameCollectionFactory.php:44)
at ApiPlatform\Core\Metadata\Property\Factory\InheritedPropertyNameCollectionFactory->create('DateInterval', array())
(vendor/api-platform/core/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php:50)
at ApiPlatform\Core\Metadata\Property\Factory\ExtractorPropertyNameCollectionFactory->create('DateInterval', array())
(vendor/api-platform/core/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php:50)
at ApiPlatform\Core\Metadata\Property\Factory\ExtractorPropertyNameCollectionFactory->create('DateInterval', array())
(vendor/api-platform/core/src/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php:47)
at ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyNameCollectionFactory->ApiPlatform\Core\Metadata\Property\Factory\{closure}()
(vendor/api-platform/core/src/Cache/CachedTrait.php:44)
at ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyNameCollectionFactory->getCached('property_name_collection_3bf8de46e9d114c9746a5da4376c201b', object(Closure))
(vendor/api-platform/core/src/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php:48)
at ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyNameCollectionFactory->create('DateInterval', array())
(vendor/api-platform/core/src/JsonSchema/SchemaFactory.php:135)
at ApiPlatform\Core\JsonSchema\SchemaFactory->buildSchema('DateInterval', 'json', 'output', null, null, object(Schema), array(), false)
(vendor/api-platform/core/src/Hydra/JsonSchema/SchemaFactory.php:52)
at ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory->buildSchema('DateInterval', 'json', 'output', null, null, object(Schema), array())
(vendor/api-platform/core/src/JsonSchema/TypeFactory.php:118)
at ApiPlatform\Core\JsonSchema\TypeFactory->getClassType('DateInterval', 'json', null, array(), object(Schema))
(vendor/api-platform/core/src/JsonSchema/TypeFactory.php:69)
at ApiPlatform\Core\JsonSchema\TypeFactory->getType(object(Type), 'json', null, array(), object(Schema))
(vendor/api-platform/core/src/JsonSchema/SchemaFactory.php:199)
at ApiPlatform\Core\JsonSchema\SchemaFactory->buildPropertySchema(object(Schema), 'Warehouse', 'operationTime', object(PropertyMetadata), array(), 'json')
(vendor/api-platform/core/src/JsonSchema/SchemaFactory.php:146)
at ApiPlatform\Core\JsonSchema\SchemaFactory->buildSchema('App\\Domain\\TourBuilding\\Model\\Entity\\Warehouse\\Warehouse', 'json', 'output', 'item', 'get', object(Schema), array(), false)
(vendor/api-platform/core/src/Hydra/JsonSchema/SchemaFactory.php:52)
at ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory->buildSchema('App\\Domain\\TourBuilding\\Model\\Entity\\Warehouse\\Warehouse', 'json', 'output', 'item', 'get', object(Schema), null, false)
(vendor/api-platform/core/src/Swagger/Serializer/DocumentationNormalizer.php:597)
at ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer->getJsonSchema(false, object(ArrayObject), 'App\\Domain\\TourBuilding\\Model\\Entity\\Warehouse\\Warehouse', 'output', 'item', 'get', 'json', null, false)
(vendor/api-platform/core/src/Swagger/Serializer/DocumentationNormalizer.php:294)
at ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer->addSchemas(false, array('description' => 'Warehouse resource response'), object(ArrayObject), 'App\\Domain\\TourBuilding\\Model\\Entity\\Warehouse\\Warehouse', 'item', 'get', array('application/ld+json' => 'jsonld', 'application/json' => 'json', 'text/html' => 'html'))
(vendor/api-platform/core/src/Swagger/Serializer/DocumentationNormalizer.php:343)
at ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer->updateGetOperation(false, object(ArrayObject), array('application/ld+json' => 'jsonld', 'application/json' => 'json', 'text/html' => 'html'), 'item', object(ResourceMetadata), 'App\\Domain\\TourBuilding\\Model\\Entity\\Warehouse\\Warehouse', 'Warehouse', 'get', object(ArrayObject))
(vendor/api-platform/core/src/Swagger/Serializer/DocumentationNormalizer.php:273)
at ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer->getPathOperation(false, 'get', array('method' => 'GET', 'input_formats' => array('jsonld' => array('application/ld+json'), 'json' => array('application/json'), 'html' => array('text/html')), 'output_formats' => array('jsonld' => array('application/ld+json'), 'json' => array('application/json'), 'html' => array('text/html'))), 'GET', 'item', 'App\\Domain\\TourBuilding\\Model\\Entity\\Warehouse\\Warehouse', object(ResourceMetadata), object(ArrayObject), object(ArrayObject))
(vendor/api-platform/core/src/Swagger/Serializer/DocumentationNormalizer.php:222)
at ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer->addPaths(false, object(ArrayObject), object(ArrayObject), 'App\\Domain\\TourBuilding\\Model\\Entity\\Warehouse\\Warehouse', 'Warehouse', object(ResourceMetadata), 'item', object(ArrayObject))
(vendor/api-platform/core/src/Swagger/Serializer/DocumentationNormalizer.php:187)
at ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer->normalize(object(Documentation), 'json', array('spec_version' => 2))
(vendor/api-platform/core/src/Swagger/Serializer/ApiGatewayNormalizer.php:51)
at ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer->normalize(object(Documentation), 'json', array('spec_version' => 2))
(vendor/symfony/serializer/Serializer.php:152)
at Symfony\Component\Serializer\Serializer->normalize(object(Documentation), 'json', array('spec_version' => 2))
(vendor/api-platform/core/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php:142)
at ApiPlatform\Core\Bridge\Symfony\Bundle\Action\SwaggerUiAction->getContext(object(Request), object(Documentation))
(vendor/api-platform/core/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php:116)
at ApiPlatform\Core\Bridge\Symfony\Bundle\Action\SwaggerUiAction->__invoke(object(Request))
(vendor/symfony/http-kernel/HttpKernel.php:146)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/http-kernel/HttpKernel.php:68)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/http-kernel/Kernel.php:201)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(public/index.php:27)
Are you sure you imported \DateInterval?
I'm sure ^^, I test my app with multiple things like phpstan lvl 7 with some strict rules, I have some phpunit tests and behat test to validate that every things work ^^, and to be sure to have what I need in attributes I use php7.4 new features.
One exmple of code where I use DateInterval
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Entity\ValueObject\Capacity;
use App\Entity\ValueObject\GeoAddress;
use App\Enum\StopTypeEnum;
use App\Enum\VehicleProfileEnum;
use DateInterval;
use Ramsey\Uuid\Uuid;
class Stop
{
private string $id;
private string $refId;
private string $type;
private ?string $contact = null;
private GeoAddress $geoAddress;
private array $restriction = VehicleProfileEnum::LIST;
private DateInterval $operationTime;
private Order $order;
public function __construct(?string $id, string $refId, Order $order, string $type, GeoAddress $geoAddress, DateInterval $operationTime, iterable $capacities)
{
$this->id = null === $id ? Uuid::uuid4()->toString() : $id;
$this->refId = $refId;
$this->order = $order;
$this->setType($type);
$this->setGeoAddress($geoAddress);
$this->setOperationTime($operationTime);
$this->setCapacities($capacities);
}
...
}
Works to me: https://github.com/soyuka/self-referencing-metadata-api-platform/blob/master/src/Entity/Dummy.php can you try use \DateInterval?
Ok, I found some new informations.
What it doesn't work it's when I set doctrine type to dateinterval:
<field name="operationTime" type="dateinterval"/>
Another thing it's when I write type as nullable with "?":
private ?DateInterval $operationTime;
It can help you ?
~I suppose you have already written a (de)normalizer for DateInterval for converting to/from ISO 8601 duration?~
EDIT: Sorry, didn't realize there's already one in Symfony: https://github.com/symfony/symfony/blob/v5.0.4/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php
FYI, Same issue with \Iterator, all related to the new way to handle the Swagger doc in 2.5.4.
Oops...
@bastnic Do you think a whitelist (for classes like \DateInterval) + blacklist (for classes like \Iterator) would solve the issue?
I'm not sure about the actual algorithm to parse everything. This is a public method on an entity that i absolutly don't want to expose, before it was not even checked, not it's checked and it fails on it. Is it possible to add a try catch and then ignore resource that fails?
before it was not even checked,
Are you sure it's really not exposed? Previously that property should have been erroneously documented as type "string".
The relevant code that has been changed is in this method: https://github.com/api-platform/core/blob/21145c7669bf2383fb370f7a05a67149ccd21fc3/src/JsonSchema/TypeFactory.php#L78
I tracked this bug it's the same as https://github.com/api-platform/core/issues/3349 see also https://github.com/api-platform/core/pull/3350
@soyuka ~No, it's not the same as #3349. I know what this bug is. (I've introduced it lol...)~
We had a chat and it seems it's #3349 after all, not a problem in my code...
But we need special handling for \DateInterval anyway. Same for \Iterator (though I'm not sure what's the right thing to do for this one...)
I don't want to create another issue if it is related, unless you tell me to,
I'm facing a similar issue with the message:
"There is no PropertyInfo extractor supporting the class "App\Message\MessageInterface". As you can imagine, the MessageInterface is an interface.
I have an existing project, and I want to expose the ConversationMessage entity as an ApiResource:
/** @ApiResource **/
class ConversationMessage
{
/** @var User **/
private $user;
}
class User
{
/** @var SomeClass **/
private $someProperty;
}
class SomeClass
{
public function getBusMessage(): ?MessageInterface
{
}
}
interface MessageInterface
{
}
There is not property related to the getBusMessage method and the App\Message namespace is excluded from the services autowiring.
I don't understand why the interface is analyzed. I'm stuck at the docs.json access...
thx
I don't want to create another issue if it is related, unless you tell me to,
I'm facing a similar issue with the message:
"There is no PropertyInfo extractor supporting the class "App\Message\MessageInterface". As you can imagine, the MessageInterface is an interface.
I have an existing project, and I want to expose the ConversationMessage entity as anApiResource:/** @ApiResource **/ class ConversationMessage { /** @var User **/ private $user; } class User { /** @var SomeClass **/ private $someProperty; } class SomeClass { public function getBusMessage(): ?MessageInterface { } } interface MessageInterface { }There is not property related to the
getBusMessagemethod and theApp\Messagenamespace is excluded from the services autowiring.I don't understand why the interface is analyzed. I'm stuck at the docs.json access...
thx
In the meantime:
ConversationMessage class using this approch: https://github.com/api-platform/core/issues/1120#issuecomment-410235533