diff --git a/features/main/table_inheritance.feature b/features/main/table_inheritance.feature index 54971c078aa..3cf9c9e46f9 100644 --- a/features/main/table_inheritance.feature +++ b/features/main/table_inheritance.feature @@ -307,15 +307,11 @@ Feature: Table inheritance "items": { "type": "object", "properties": { - "@type": { - "type": "string", - "pattern": "^ResourceInterface$" - }, - "@id": { + "foo": { "type": "string", - "pattern": "^_:" + "required": "true" }, - "foo": { + "fooz": { "type": "string", "required": "true" } @@ -327,3 +323,30 @@ Feature: Table inheritance "required": ["hydra:member"] } """ + + Scenario: Get an interface resource item + When I send a "GET" request to "/resource_interfaces/some-id" + Then the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be valid according to this schema: + """ + { + "type": "object", + "properties": { + "context": { + "type": "string", + "pattern": "ResourceInterface$" + }, + "foo": { + "type": "string", + "required": "true" + }, + "fooz": { + "type": "string", + "required": "true", + "pattern": "fooz" + } + } + } + """ diff --git a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php index ee886f80e60..ecf31999fd8 100644 --- a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php @@ -48,9 +48,11 @@ public function create(string $resourceClass, string $property, array $options = } } + $isInterface = interface_exists($resourceClass); + if ( - !property_exists($resourceClass, $property) || - !$propertyMetadata = $this->extractor->getResources()[$resourceClass]['properties'][$property] ?? false + !property_exists($resourceClass, $property) && !$isInterface || + null === ($propertyMetadata = $this->extractor->getResources()[$resourceClass]['properties'][$property] ?? null) ) { return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property); } diff --git a/tests/Fixtures/FileConfigurations/interface_resource.yml b/tests/Fixtures/FileConfigurations/interface_resource.yml index 40c2b44b814..9f0afd51cfc 100644 --- a/tests/Fixtures/FileConfigurations/interface_resource.yml +++ b/tests/Fixtures/FileConfigurations/interface_resource.yml @@ -1,2 +1,8 @@ resources: - 'ApiPlatform\Core\Tests\Fixtures\DummyResourceInterface': ~ + 'ApiPlatform\Core\Tests\Fixtures\DummyResourceInterface': + properties: + something: + identifier: true + somethingElse: + writable: false + readable: true diff --git a/tests/Fixtures/TestBundle/DataProvider/ResourceInterfaceImplementationDataProvider.php b/tests/Fixtures/TestBundle/DataProvider/ResourceInterfaceImplementationDataProvider.php index 613ef8f5ec9..2804452b242 100644 --- a/tests/Fixtures/TestBundle/DataProvider/ResourceInterfaceImplementationDataProvider.php +++ b/tests/Fixtures/TestBundle/DataProvider/ResourceInterfaceImplementationDataProvider.php @@ -28,7 +28,11 @@ public function supports(string $resourceClass, string $operationName = null, ar public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) { - return (new ResourceInterfaceImplementation())->setFoo('single item'); + if ('some-id' === $id) { + return (new ResourceInterfaceImplementation())->setFoo('single item'); + } + + return null; } public function getCollection(string $resourceClass, string $operationName = null) diff --git a/tests/Fixtures/TestBundle/OtherResources/ResourceInterface.php b/tests/Fixtures/TestBundle/OtherResources/ResourceInterface.php index edd4162de5d..9335c41f4c4 100644 --- a/tests/Fixtures/TestBundle/OtherResources/ResourceInterface.php +++ b/tests/Fixtures/TestBundle/OtherResources/ResourceInterface.php @@ -16,4 +16,6 @@ interface ResourceInterface { public function getFoo(): string; + + public function getFooz(): string; } diff --git a/tests/Fixtures/TestBundle/OtherResources/ResourceInterfaceImplementation.php b/tests/Fixtures/TestBundle/OtherResources/ResourceInterfaceImplementation.php index e5e571dc27f..afec2000958 100644 --- a/tests/Fixtures/TestBundle/OtherResources/ResourceInterfaceImplementation.php +++ b/tests/Fixtures/TestBundle/OtherResources/ResourceInterfaceImplementation.php @@ -51,4 +51,9 @@ public function getBar(): ?string { return $this->bar; } + + public function getFooz(): string + { + return 'fooz'; + } } diff --git a/tests/Fixtures/TestBundle/Resources/config/api_resources.yml b/tests/Fixtures/TestBundle/Resources/config/api_resources.yml index d504f5e2b39..5bf4b7afadd 100644 --- a/tests/Fixtures/TestBundle/Resources/config/api_resources.yml +++ b/tests/Fixtures/TestBundle/Resources/config/api_resources.yml @@ -7,3 +7,7 @@ resources: collectionOperations: get: method: 'GET' + + properties: + foo: + identifier: true diff --git a/tests/Metadata/Property/Factory/ExtractorPropertyMetadataFactoryTest.php b/tests/Metadata/Property/Factory/ExtractorPropertyMetadataFactoryTest.php index e420be7e3de..82cd34f8f33 100644 --- a/tests/Metadata/Property/Factory/ExtractorPropertyMetadataFactoryTest.php +++ b/tests/Metadata/Property/Factory/ExtractorPropertyMetadataFactoryTest.php @@ -21,6 +21,7 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\PropertyMetadata; use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; +use ApiPlatform\Core\Tests\Fixtures\DummyResourceInterface; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; use Doctrine\Common\Collections\ArrayCollection; @@ -237,4 +238,18 @@ public function testCreateWithMalformedYaml() (new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class, 'foo'); } + + public function testItExtractPropertiesFromInterfaceResources() + { + $configPath = __DIR__.'/../../../Fixtures/FileConfigurations/interface_resource.yml'; + + $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])); + $metadataSomething = $propertyMetadataFactory->create(DummyResourceInterface::class, 'something'); + $metadataSomethingElse = $propertyMetadataFactory->create(DummyResourceInterface::class, 'somethingElse'); + + $this->assertInstanceOf(PropertyMetadata::class, $metadataSomething); + $this->assertInstanceOf(PropertyMetadata::class, $metadataSomethingElse); + $this->assertTrue($metadataSomething->isIdentifier()); + $this->assertFalse($metadataSomethingElse->isWritable()); + } }