From 195c4e7883520416e042ac78143b18652a216fbf Mon Sep 17 00:00:00 2001 From: valentin-dassonville <129871973+valentin-dassonville@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:20:37 +0200 Subject: [PATCH 1/2] fix(hydra): hydra context changed (#6710) Co-authored-by: Valentin Dassonville --- features/hydra/docs.feature | 31 ++- .../Serializer/DocumentationNormalizer.php | 31 +-- .../DocumentationNormalizerTest.php | 177 ++++++++---------- src/JsonLd/ContextBuilder.php | 2 +- src/JsonLd/ContextBuilderInterface.php | 1 + 5 files changed, 113 insertions(+), 129 deletions(-) diff --git a/features/hydra/docs.feature b/features/hydra/docs.feature index d848ac55969..6e3501d3bca 100644 --- a/features/hydra/docs.feature +++ b/features/hydra/docs.feature @@ -13,22 +13,19 @@ Feature: Documentation support And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" # Context - And the JSON node "@context.@vocab" should be equal to "http://example.com/docs.jsonld#" - And the JSON node "@context.hydra" should be equal to "http://www.w3.org/ns/hydra/core#" - And the JSON node "@context.rdf" should be equal to "http://www.w3.org/1999/02/22-rdf-syntax-ns#" - And the JSON node "@context.rdfs" should be equal to "http://www.w3.org/2000/01/rdf-schema#" - And the JSON node "@context.xmls" should be equal to "http://www.w3.org/2001/XMLSchema#" - And the JSON node "@context.owl" should be equal to "http://www.w3.org/2002/07/owl#" - And the JSON node "@context.domain.@id" should be equal to "rdfs:domain" - And the JSON node "@context.domain.@type" should be equal to "@id" - And the JSON node "@context.range.@id" should be equal to "rdfs:range" - And the JSON node "@context.range.@type" should be equal to "@id" - And the JSON node "@context.subClassOf.@id" should be equal to "rdfs:subClassOf" - And the JSON node "@context.subClassOf.@type" should be equal to "@id" - And the JSON node "@context.expects.@id" should be equal to "hydra:expects" - And the JSON node "@context.expects.@type" should be equal to "@id" - And the JSON node "@context.returns.@id" should be equal to "hydra:returns" - And the JSON node "@context.returns.@type" should be equal to "@id" + And the JSON node "@context[0]" should be equal to "http://www.w3.org/ns/hydra/context.jsonld" + And the JSON node "@context[1].@vocab" should be equal to "http://example.com/docs.jsonld#" + And the JSON node "@context[1].hydra" should be equal to "http://www.w3.org/ns/hydra/core#" + And the JSON node "@context[1].rdf" should be equal to "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + And the JSON node "@context[1].rdfs" should be equal to "http://www.w3.org/2000/01/rdf-schema#" + And the JSON node "@context[1].xmls" should be equal to "http://www.w3.org/2001/XMLSchema#" + And the JSON node "@context[1].owl" should be equal to "http://www.w3.org/2002/07/owl#" + And the JSON node "@context[1].domain.@id" should be equal to "rdfs:domain" + And the JSON node "@context[1].domain.@type" should be equal to "@id" + And the JSON node "@context[1].range.@id" should be equal to "rdfs:range" + And the JSON node "@context[1].range.@type" should be equal to "@id" + And the JSON node "@context[1].subClassOf.@id" should be equal to "rdfs:subClassOf" + And the JSON node "@context[1].subClassOf.@type" should be equal to "@id" # Root properties And the JSON node "@id" should be equal to "/docs.jsonld" And the JSON node "hydra:title" should be equal to "My Dummy API" @@ -79,7 +76,7 @@ Feature: Documentation support And the value of the node "hydra:method" of the operation "GET" of the Hydra class "Dummy" is "GET" And the value of the node "hydra:title" of the operation "GET" of the Hydra class "Dummy" is "Retrieves a Dummy resource." And the value of the node "rdfs:label" of the operation "GET" of the Hydra class "Dummy" is "Retrieves a Dummy resource." - And the value of the node "returns" of the operation "GET" of the Hydra class "Dummy" is "#Dummy" + And the value of the node "returns" of the operation "GET" of the Hydra class "Dummy" is "Dummy" And the value of the node "hydra:title" of the operation "PUT" of the Hydra class "Dummy" is "Replaces the Dummy resource." And the value of the node "hydra:title" of the operation "DELETE" of the Hydra class "Dummy" is "Deletes the Dummy resource." And the value of the node "returns" of the operation "DELETE" of the Hydra class "Dummy" is "owl:Nothing" diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index f27a1a10205..0cae690f683 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -74,6 +74,7 @@ public function normalize(mixed $object, ?string $format = null, array $context } $shortName = $resourceMetadata->getShortName(); + $prefixedShortName = $resourceMetadata->getTypes()[0] ?? "#$shortName"; $this->populateEntrypointProperties($resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties, $hydraPrefix, $resourceMetadataCollection); $classes[] = $this->getClass($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context, $hydraPrefix, $resourceMetadataCollection); @@ -243,8 +244,7 @@ private function getHydraOperations(bool $collection, ?ResourceMetadataCollectio if (('POST' === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) { continue; } - - $hydraOperations[] = $this->getHydraOperation($operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}", $hydraPrefix); + $hydraOperations[] = $this->getHydraOperation($operation, $operation->getShortName(), $hydraPrefix); } } @@ -430,7 +430,7 @@ private function getClasses(array $entrypointProperties, array $classes, string '@type' => $hydraPrefix.'Operation', $hydraPrefix.'method' => 'GET', 'rdfs:label' => 'The API entrypoint.', - 'returns' => '#EntryPoint', + 'returns' => 'EntryPoint', ], ]; @@ -573,18 +573,19 @@ private function computeDoc(Documentation $object, array $classes, string $hydra private function getContext(string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array { return [ - '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#', - 'hydra' => ContextBuilderInterface::HYDRA_NS, - 'rdf' => ContextBuilderInterface::RDF_NS, - 'rdfs' => ContextBuilderInterface::RDFS_NS, - 'xmls' => ContextBuilderInterface::XML_NS, - 'owl' => ContextBuilderInterface::OWL_NS, - 'schema' => ContextBuilderInterface::SCHEMA_ORG_NS, - 'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'], - 'range' => ['@id' => 'rdfs:range', '@type' => '@id'], - 'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'], - 'expects' => ['@id' => $hydraPrefix.'expects', '@type' => '@id'], - 'returns' => ['@id' => $hydraPrefix.'returns', '@type' => '@id'], + ContextBuilderInterface::HYDRA_CONTEXT, + [ + '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#', + 'hydra' => ContextBuilderInterface::HYDRA_NS, + 'rdf' => ContextBuilderInterface::RDF_NS, + 'rdfs' => ContextBuilderInterface::RDFS_NS, + 'xmls' => ContextBuilderInterface::XML_NS, + 'owl' => ContextBuilderInterface::OWL_NS, + 'schema' => ContextBuilderInterface::SCHEMA_ORG_NS, + 'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'], + 'range' => ['@id' => 'rdfs:range', '@type' => '@id'], + 'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'], + ], ]; } diff --git a/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php b/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php index b3e8b4a1f7e..f54c56a0bbe 100644 --- a/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php +++ b/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php @@ -106,32 +106,27 @@ private function doTestNormalize($resourceMetadataFactory = null): void $expected = [ '@context' => [ - '@vocab' => '/doc#', - 'hydra' => 'http://www.w3.org/ns/hydra/core#', - 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', - 'xmls' => 'http://www.w3.org/2001/XMLSchema#', - 'owl' => 'http://www.w3.org/2002/07/owl#', - 'schema' => 'https://schema.org/', - 'domain' => [ - '@id' => 'rdfs:domain', - '@type' => '@id', - ], - 'range' => [ - '@id' => 'rdfs:range', - '@type' => '@id', - ], - 'subClassOf' => [ - '@id' => 'rdfs:subClassOf', - '@type' => '@id', - ], - 'expects' => [ - '@id' => 'hydra:expects', - '@type' => '@id', - ], - 'returns' => [ - '@id' => 'hydra:returns', - '@type' => '@id', + 'http://www.w3.org/ns/hydra/context.jsonld', + [ + '@vocab' => '/doc#', + 'hydra' => 'http://www.w3.org/ns/hydra/core#', + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', + 'xmls' => 'http://www.w3.org/2001/XMLSchema#', + 'owl' => 'http://www.w3.org/2002/07/owl#', + 'schema' => 'https://schema.org/', + 'domain' => [ + '@id' => 'rdfs:domain', + '@type' => '@id', + ], + 'range' => [ + '@id' => 'rdfs:range', + '@type' => '@id', + ], + 'subClassOf' => [ + '@id' => 'rdfs:subClassOf', + '@type' => '@id', + ], ], ], '@id' => '/doc', @@ -226,23 +221,23 @@ private function doTestNormalize($resourceMetadataFactory = null): void 'hydra:method' => 'GET', 'hydra:title' => 'foobar', 'rdfs:label' => 'foobar', - 'returns' => '#dummy', + 'returns' => 'dummy', 'hydra:foo' => 'bar', ], [ '@type' => ['hydra:Operation', 'schema:ReplaceAction'], - 'expects' => '#dummy', + 'expects' => 'dummy', 'hydra:method' => 'PUT', 'hydra:title' => 'Replaces the dummy resource.', 'rdfs:label' => 'Replaces the dummy resource.', - 'returns' => '#dummy', + 'returns' => 'dummy', ], [ '@type' => ['hydra:Operation', 'schema:FindAction'], 'hydra:method' => 'GET', 'hydra:title' => 'Retrieves a relatedDummy resource.', 'rdfs:label' => 'Retrieves a relatedDummy resource.', - 'returns' => '#relatedDummy', + 'returns' => 'relatedDummy', ], ], ], @@ -277,11 +272,11 @@ private function doTestNormalize($resourceMetadataFactory = null): void ], [ '@type' => ['hydra:Operation', 'schema:CreateAction'], - 'expects' => '#dummy', + 'expects' => 'dummy', 'hydra:method' => 'POST', 'hydra:title' => 'Creates a dummy resource.', 'rdfs:label' => 'Creates a dummy resource.', - 'returns' => '#dummy', + 'returns' => 'dummy', ], ], ], @@ -294,7 +289,7 @@ private function doTestNormalize($resourceMetadataFactory = null): void '@type' => 'hydra:Operation', 'hydra:method' => 'GET', 'rdfs:label' => 'The API entrypoint.', - 'returns' => '#EntryPoint', + 'returns' => 'EntryPoint', ], ], [ @@ -411,32 +406,27 @@ public function testNormalizeInputOutputClass(): void $expected = [ '@context' => [ - '@vocab' => '/doc#', - 'hydra' => 'http://www.w3.org/ns/hydra/core#', - 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', - 'xmls' => 'http://www.w3.org/2001/XMLSchema#', - 'owl' => 'http://www.w3.org/2002/07/owl#', - 'schema' => 'https://schema.org/', - 'domain' => [ - '@id' => 'rdfs:domain', - '@type' => '@id', - ], - 'range' => [ - '@id' => 'rdfs:range', - '@type' => '@id', - ], - 'subClassOf' => [ - '@id' => 'rdfs:subClassOf', - '@type' => '@id', - ], - 'expects' => [ - '@id' => 'hydra:expects', - '@type' => '@id', - ], - 'returns' => [ - '@id' => 'hydra:returns', - '@type' => '@id', + 'http://www.w3.org/ns/hydra/context.jsonld', + [ + '@vocab' => '/doc#', + 'hydra' => 'http://www.w3.org/ns/hydra/core#', + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', + 'xmls' => 'http://www.w3.org/2001/XMLSchema#', + 'owl' => 'http://www.w3.org/2002/07/owl#', + 'schema' => 'https://schema.org/', + 'domain' => [ + '@id' => 'rdfs:domain', + '@type' => '@id', + ], + 'range' => [ + '@id' => 'rdfs:range', + '@type' => '@id', + ], + 'subClassOf' => [ + '@id' => 'rdfs:subClassOf', + '@type' => '@id', + ], ], ], '@id' => '/doc', @@ -521,7 +511,7 @@ public function testNormalizeInputOutputClass(): void 'hydra:method' => 'GET', 'hydra:title' => 'Retrieves a dummy resource.', 'rdfs:label' => 'Retrieves a dummy resource.', - 'returns' => '#dummy', + 'returns' => 'dummy', ], [ '@type' => [ @@ -532,7 +522,7 @@ public function testNormalizeInputOutputClass(): void 'hydra:method' => 'PUT', 'hydra:title' => 'Replaces the dummy resource.', 'rdfs:label' => 'Replaces the dummy resource.', - 'returns' => '#dummy', + 'returns' => 'dummy', ], ], 'hydra:description' => 'dummy', @@ -580,7 +570,7 @@ public function testNormalizeInputOutputClass(): void 'hydra:Operation', 'schema:CreateAction', ], - 'expects' => '#dummy', + 'expects' => 'dummy', 'hydra:method' => 'POST', 'hydra:title' => 'Creates a dummy resource.', 'rdfs:label' => 'Creates a dummy resource.', @@ -597,7 +587,7 @@ public function testNormalizeInputOutputClass(): void '@type' => 'hydra:Operation', 'hydra:method' => 'GET', 'rdfs:label' => 'The API entrypoint.', - 'returns' => '#EntryPoint', + 'returns' => 'EntryPoint', ], ], 2 => [ @@ -776,32 +766,27 @@ public function testNormalizeWithoutPrefix(): void $expected = [ '@context' => [ - '@vocab' => '/doc#', - 'hydra' => 'http://www.w3.org/ns/hydra/core#', - 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', - 'xmls' => 'http://www.w3.org/2001/XMLSchema#', - 'owl' => 'http://www.w3.org/2002/07/owl#', - 'schema' => 'https://schema.org/', - 'domain' => [ - '@id' => 'rdfs:domain', - '@type' => '@id', - ], - 'range' => [ - '@id' => 'rdfs:range', - '@type' => '@id', - ], - 'subClassOf' => [ - '@id' => 'rdfs:subClassOf', - '@type' => '@id', - ], - 'expects' => [ - '@id' => 'expects', - '@type' => '@id', - ], - 'returns' => [ - '@id' => 'returns', - '@type' => '@id', + 'http://www.w3.org/ns/hydra/context.jsonld', + [ + '@vocab' => '/doc#', + 'hydra' => 'http://www.w3.org/ns/hydra/core#', + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', + 'xmls' => 'http://www.w3.org/2001/XMLSchema#', + 'owl' => 'http://www.w3.org/2002/07/owl#', + 'schema' => 'https://schema.org/', + 'domain' => [ + '@id' => 'rdfs:domain', + '@type' => '@id', + ], + 'range' => [ + '@id' => 'rdfs:range', + '@type' => '@id', + ], + 'subClassOf' => [ + '@id' => 'rdfs:subClassOf', + '@type' => '@id', + ], ], ], '@id' => '/doc', @@ -896,23 +881,23 @@ public function testNormalizeWithoutPrefix(): void 'method' => 'GET', 'title' => 'foobar', 'rdfs:label' => 'foobar', - 'returns' => '#dummy', + 'returns' => 'dummy', 'foo' => 'bar', ], [ '@type' => ['Operation', 'schema:ReplaceAction'], - 'expects' => '#dummy', + 'expects' => 'dummy', 'method' => 'PUT', 'title' => 'Replaces the dummy resource.', 'rdfs:label' => 'Replaces the dummy resource.', - 'returns' => '#dummy', + 'returns' => 'dummy', ], [ '@type' => ['Operation', 'schema:FindAction'], 'method' => 'GET', 'title' => 'Retrieves a relatedDummy resource.', 'rdfs:label' => 'Retrieves a relatedDummy resource.', - 'returns' => '#relatedDummy', + 'returns' => 'relatedDummy', ], ], ], @@ -947,11 +932,11 @@ public function testNormalizeWithoutPrefix(): void ], [ '@type' => ['Operation', 'schema:CreateAction'], - 'expects' => '#dummy', + 'expects' => 'dummy', 'method' => 'POST', 'title' => 'Creates a dummy resource.', 'rdfs:label' => 'Creates a dummy resource.', - 'returns' => '#dummy', + 'returns' => 'dummy', ], ], ], @@ -964,7 +949,7 @@ public function testNormalizeWithoutPrefix(): void '@type' => 'Operation', 'method' => 'GET', 'rdfs:label' => 'The API entrypoint.', - 'returns' => '#EntryPoint', + 'returns' => 'EntryPoint', ], ], [ diff --git a/src/JsonLd/ContextBuilder.php b/src/JsonLd/ContextBuilder.php index 5cb591d0fab..bfc6e5765ba 100644 --- a/src/JsonLd/ContextBuilder.php +++ b/src/JsonLd/ContextBuilder.php @@ -185,7 +185,7 @@ private function getResourceContextWithShortname(string $resourceClass, int $ref } if (false === ($this->defaultContext[self::HYDRA_CONTEXT_HAS_PREFIX] ?? true)) { - return ['http://www.w3.org/ns/hydra/context.jsonld', $context]; + return [ContextBuilderInterface::HYDRA_CONTEXT, $context]; } return $context; diff --git a/src/JsonLd/ContextBuilderInterface.php b/src/JsonLd/ContextBuilderInterface.php index 63c34d66c42..c90152c1f83 100644 --- a/src/JsonLd/ContextBuilderInterface.php +++ b/src/JsonLd/ContextBuilderInterface.php @@ -23,6 +23,7 @@ */ interface ContextBuilderInterface { + public const HYDRA_CONTEXT = 'http://www.w3.org/ns/hydra/context.jsonld'; public const HYDRA_NS = 'http://www.w3.org/ns/hydra/core#'; public const JSONLD_NS = 'http://www.w3.org/ns/json-ld#'; public const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; From ac6f667f301f6c4c399a707faf00567239bd98d8 Mon Sep 17 00:00:00 2001 From: Amer Chaudhary Date: Wed, 23 Oct 2024 17:29:25 +0500 Subject: [PATCH 2/2] fix(laravel): collection relations other than HasMany (#6737) --- .../Property/EloquentPropertyMetadataFactory.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Laravel/Eloquent/Metadata/Factory/Property/EloquentPropertyMetadataFactory.php b/src/Laravel/Eloquent/Metadata/Factory/Property/EloquentPropertyMetadataFactory.php index b0aee59ee8f..c88c96585e2 100644 --- a/src/Laravel/Eloquent/Metadata/Factory/Property/EloquentPropertyMetadataFactory.php +++ b/src/Laravel/Eloquent/Metadata/Factory/Property/EloquentPropertyMetadataFactory.php @@ -18,7 +18,11 @@ use ApiPlatform\Metadata\Exception\PropertyNotFoundException; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; +use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Support\Collection; use Symfony\Component\PropertyInfo\Type; @@ -92,10 +96,14 @@ public function create(string $resourceClass, string $property, array $options = continue; } - $collection = false; - if (HasMany::class === $relation['type']) { - $collection = true; - } + $collection = match ($relation['type']) { + HasMany::class, + HasManyThrough::class, + BelongsToMany::class, + MorphMany::class, + MorphToMany::class => true, + default => false, + }; $type = new Type($collection ? Type::BUILTIN_TYPE_ITERABLE : Type::BUILTIN_TYPE_OBJECT, false, $relation['related'], $collection, collectionValueType: new Type(Type::BUILTIN_TYPE_OBJECT, false, $relation['related']));