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#'; 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']));