diff --git a/src/JsonLd/ContextBuilder.php b/src/JsonLd/ContextBuilder.php index 4f35aa6705..65fa1f5898 100644 --- a/src/JsonLd/ContextBuilder.php +++ b/src/JsonLd/ContextBuilder.php @@ -180,6 +180,11 @@ private function generateContextUri(?string $shortName, ?int $referenceType): st private function getResourceContextWithShortname(string $resourceClass, int $referenceType, string $shortName, ?HttpOperation $operation = null): array { $context = $this->getBaseContext($referenceType); + + if ($operation && $jsonldContext = $operation->getJsonldContext()) { + $context = array_merge($context, $jsonldContext); + } + $propertyContext = $operation ? ['normalization_groups' => $operation->getNormalizationContext()['groups'] ?? null, 'denormalization_groups' => $operation->getDenormalizationContext()['groups'] ?? null] : ['normalization_groups' => [], 'denormalization_groups' => []]; foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { diff --git a/src/Metadata/ApiResource.php b/src/Metadata/ApiResource.php index eabfdda8fc..32922afb1d 100644 --- a/src/Metadata/ApiResource.php +++ b/src/Metadata/ApiResource.php @@ -326,6 +326,14 @@ public function __construct( protected ?array $denormalizationContext = null, protected ?bool $collectDenormalizationErrors = null, protected ?array $hydraContext = null, + /** + * Extra entries to merge into the JSON-LD `@context` for this resource (e.g. namespace prefix declarations). + * + * Example: `jsonldContext: ['dct' => 'http://purl.org/dc/terms/']` + * + * @see https://api-platform.com/docs/core/extending-jsonld-context/ + */ + protected ?array $jsonldContext = null, protected bool|OpenApiOperation|null $openapi = null, /** * The `validationContext` option configures the context of validation for the current ApiResource. @@ -1373,6 +1381,19 @@ public function withHydraContext(array $hydraContext): static return $self; } + public function getJsonldContext(): ?array + { + return $this->jsonldContext; + } + + public function withJsonldContext(array $jsonldContext): static + { + $self = clone $this; + $self->jsonldContext = $jsonldContext; + + return $self; + } + public function getOpenapi(): bool|OpenApiOperation|null { return $this->openapi; diff --git a/src/Metadata/Delete.php b/src/Metadata/Delete.php index b4e55ef676..3674a5d6fe 100644 --- a/src/Metadata/Delete.php +++ b/src/Metadata/Delete.php @@ -44,6 +44,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -129,6 +130,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/src/Metadata/Error.php b/src/Metadata/Error.php index dabe1b854d..c7c3473345 100644 --- a/src/Metadata/Error.php +++ b/src/Metadata/Error.php @@ -44,6 +44,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -123,6 +124,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/src/Metadata/ErrorResource.php b/src/Metadata/ErrorResource.php index 8f1586ac03..c370071361 100644 --- a/src/Metadata/ErrorResource.php +++ b/src/Metadata/ErrorResource.php @@ -49,6 +49,7 @@ public function __construct( ?array $denormalizationContext = null, ?bool $collectDenormalizationErrors = null, ?array $hydraContext = null, + ?array $jsonldContext = null, OpenApiOperation|bool|null $openapi = null, ?array $validationContext = null, ?array $filters = null, @@ -116,6 +117,7 @@ class: $class, denormalizationContext: $denormalizationContext, collectDenormalizationErrors: $collectDenormalizationErrors, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, validationContext: $validationContext, filters: $filters, diff --git a/src/Metadata/Extractor/XmlResourceExtractor.php b/src/Metadata/Extractor/XmlResourceExtractor.php index 6ebc1a66bf..4d3c3206b5 100644 --- a/src/Metadata/Extractor/XmlResourceExtractor.php +++ b/src/Metadata/Extractor/XmlResourceExtractor.php @@ -93,6 +93,7 @@ private function buildExtendedBase(\SimpleXMLElement $resource): array 'schemes' => $this->buildArrayValue($resource, 'scheme'), 'cacheHeaders' => $this->buildCacheHeaders($resource), 'hydraContext' => isset($resource->hydraContext->values) ? $this->buildValues($resource->hydraContext->values) : null, + 'jsonldContext' => isset($resource->jsonldContext->values) ? $this->buildValues($resource->jsonldContext->values) : null, 'openapi' => $this->buildOpenapi($resource), 'paginationViaCursor' => $this->buildPaginationViaCursor($resource), 'exceptionToStatus' => $this->buildExceptionToStatus($resource), diff --git a/src/Metadata/Extractor/YamlResourceExtractor.php b/src/Metadata/Extractor/YamlResourceExtractor.php index e7dd40093c..67848c5694 100644 --- a/src/Metadata/Extractor/YamlResourceExtractor.php +++ b/src/Metadata/Extractor/YamlResourceExtractor.php @@ -114,6 +114,7 @@ private function buildExtendedBase(array $resource): array 'types' => $this->buildArrayValue($resource, 'types'), 'cacheHeaders' => $this->buildArrayValue($resource, 'cacheHeaders'), 'hydraContext' => $this->buildArrayValue($resource, 'hydraContext'), + 'jsonldContext' => $this->buildArrayValue($resource, 'jsonldContext'), 'openapi' => $this->buildOpenapi($resource), 'paginationViaCursor' => $this->buildArrayValue($resource, 'paginationViaCursor'), 'exceptionToStatus' => $this->buildArrayValue($resource, 'exceptionToStatus'), diff --git a/src/Metadata/Get.php b/src/Metadata/Get.php index 4babd54eb2..4c59d7ab95 100644 --- a/src/Metadata/Get.php +++ b/src/Metadata/Get.php @@ -44,6 +44,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -128,6 +129,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/src/Metadata/GetCollection.php b/src/Metadata/GetCollection.php index 27df4b9ad4..6256366bd2 100644 --- a/src/Metadata/GetCollection.php +++ b/src/Metadata/GetCollection.php @@ -44,6 +44,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -129,6 +130,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php index 58d4cf98c7..32dfa15bb7 100644 --- a/src/Metadata/HttpOperation.php +++ b/src/Metadata/HttpOperation.php @@ -164,6 +164,7 @@ public function __construct( protected ?array $cacheHeaders = null, protected ?array $paginationViaCursor = null, protected ?array $hydraContext = null, + protected ?array $jsonldContext = null, protected bool|OpenApiOperation|Webhook|null $openapi = null, protected ?array $exceptionToStatus = null, protected ?array $links = null, @@ -629,6 +630,19 @@ public function withHydraContext(array $hydraContext): static return $self; } + public function getJsonldContext(): ?array + { + return $this->jsonldContext; + } + + public function withJsonldContext(array $jsonldContext): static + { + $self = clone $this; + $self->jsonldContext = $jsonldContext; + + return $self; + } + public function getOpenapi(): bool|OpenApiOperation|Webhook|null { return $this->openapi; diff --git a/src/Metadata/McpResource.php b/src/Metadata/McpResource.php index c36342c1e6..5be8ab91f3 100644 --- a/src/Metadata/McpResource.php +++ b/src/Metadata/McpResource.php @@ -126,6 +126,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?array $links = null, @@ -209,6 +210,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, links: $links, diff --git a/src/Metadata/McpTool.php b/src/Metadata/McpTool.php index 465da19d76..f46f7a297d 100644 --- a/src/Metadata/McpTool.php +++ b/src/Metadata/McpTool.php @@ -122,6 +122,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?array $links = null, @@ -205,6 +206,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, links: $links, diff --git a/src/Metadata/NotExposed.php b/src/Metadata/NotExposed.php index e106aa23b4..c3422bac24 100644 --- a/src/Metadata/NotExposed.php +++ b/src/Metadata/NotExposed.php @@ -56,6 +56,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = false, ?array $exceptionToStatus = null, @@ -135,6 +136,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/src/Metadata/Patch.php b/src/Metadata/Patch.php index 13d7dc442a..e6147a18da 100644 --- a/src/Metadata/Patch.php +++ b/src/Metadata/Patch.php @@ -44,6 +44,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -129,6 +130,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/src/Metadata/Post.php b/src/Metadata/Post.php index 419512a851..e68e4b0ec6 100644 --- a/src/Metadata/Post.php +++ b/src/Metadata/Post.php @@ -44,6 +44,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -130,6 +131,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/src/Metadata/Put.php b/src/Metadata/Put.php index 3ea21ffead..73632c786b 100644 --- a/src/Metadata/Put.php +++ b/src/Metadata/Put.php @@ -44,6 +44,7 @@ public function __construct( ?array $cacheHeaders = null, ?array $paginationViaCursor = null, ?array $hydraContext = null, + ?array $jsonldContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -130,6 +131,7 @@ public function __construct( cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, + jsonldContext: $jsonldContext, openapi: $openapi, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, diff --git a/tests/Fixtures/TestBundle/ApiResource/JsonLd/JsonLdContextDummy.php b/tests/Fixtures/TestBundle/ApiResource/JsonLd/JsonLdContextDummy.php index 18cd539f17..ab545ac693 100644 --- a/tests/Fixtures/TestBundle/ApiResource/JsonLd/JsonLdContextDummy.php +++ b/tests/Fixtures/TestBundle/ApiResource/JsonLd/JsonLdContextDummy.php @@ -20,6 +20,7 @@ shortName: 'JsonLdContextDummy', provider: [self::class, 'provide'], processor: [self::class, 'process'], + jsonldContext: ['dct' => 'http://purl.org/dc/terms/'], )] class JsonLdContextDummy { @@ -29,6 +30,9 @@ class JsonLdContextDummy #[ApiProperty(iris: ['https://schema.org/name'])] public ?string $name = null; + #[ApiProperty(iris: ['dct:title'])] + public ?string $title = null; + #[ApiProperty(iris: ['https://schema.org/alternateName'])] public ?string $alias = null; diff --git a/tests/Functional/JsonLd/ContextTest.php b/tests/Functional/JsonLd/ContextTest.php index dd768a9cf5..6906f979db 100644 --- a/tests/Functional/JsonLd/ContextTest.php +++ b/tests/Functional/JsonLd/ContextTest.php @@ -112,4 +112,13 @@ public function testEmbeddedRelationMappingIsPlainString(): void $body = $response->toArray(); $this->assertSame('JsonLdContextDummy/embedded', $body['@context']['embedded']); } + + public function testResourceLevelJsonLdContextAddsNamespacePrefixes(): void + { + $response = self::createClient()->request('GET', '/contexts/JsonLdContextDummy'); + $this->assertResponseIsSuccessful(); + $body = $response->toArray(); + $this->assertSame('http://purl.org/dc/terms/', $body['@context']['dct']); + $this->assertSame('dct:title', $body['@context']['title']); + } }