diff --git a/src/JsonLd/ContextBuilder.php b/src/JsonLd/ContextBuilder.php
index 4f35aa67057..65fa1f58985 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 eabfdda8fc0..32922afb1d7 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 b4e55ef6765..3674a5d6fe9 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 dabe1b854d5..c7c34733459 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 8f1586ac038..c3700713617 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 6ebc1a66bf8..4d3c3206b53 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 e7dd40093c8..67848c56942 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/Extractor/schema/resources.xsd b/src/Metadata/Extractor/schema/resources.xsd
index 02468b51bd7..8a1644c4790 100644
--- a/src/Metadata/Extractor/schema/resources.xsd
+++ b/src/Metadata/Extractor/schema/resources.xsd
@@ -479,6 +479,7 @@
+
diff --git a/src/Metadata/Get.php b/src/Metadata/Get.php
index 4babd54eb27..4c59d7ab957 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 27df4b9ad41..6256366bd27 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 58d4cf98c7f..32dfa15bb7e 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 c36342c1e6b..5be8ab91f35 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 465da19d76f..f46f7a297d8 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 e106aa23b4e..c3422bac243 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 13d7dc442a0..e6147a18dad 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 419512a851d..e68e4b0ec66 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 3ea21ffeadd..73632c786bc 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/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php b/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php
index b15d99a4508..6e3a1296f1b 100644
--- a/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php
+++ b/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php
@@ -230,6 +230,11 @@ private function buildHydraContext(\SimpleXMLElement $resource, array $values):
$this->buildValues($resource->addChild('hydraContext'), $values);
}
+ private function buildJsonldContext(\SimpleXMLElement $resource, array $values): void
+ {
+ $this->buildValues($resource->addChild('jsonldContext'), $values);
+ }
+
private function buildOpenapi(\SimpleXMLElement $resource, array $values): void
{
$node = $resource->openapi ?? $resource->addChild('openapi');
diff --git a/src/Metadata/Tests/Extractor/Adapter/resources.xml b/src/Metadata/Tests/Extractor/Adapter/resources.xml
index b7e83452477..06c90ebfd20 100644
--- a/src/Metadata/Tests/Extractor/Adapter/resources.xml
+++ b/src/Metadata/Tests/Extractor/Adapter/resources.xml
@@ -1,3 +1,3 @@
-
+
diff --git a/src/Metadata/Tests/Extractor/Adapter/resources.yaml b/src/Metadata/Tests/Extractor/Adapter/resources.yaml
index 30c16895a07..6dc74676c48 100644
--- a/src/Metadata/Tests/Extractor/Adapter/resources.yaml
+++ b/src/Metadata/Tests/Extractor/Adapter/resources.yaml
@@ -66,6 +66,8 @@ resources:
hydraContext:
foo:
bar: baz
+ jsonldContext:
+ dct: 'http://purl.org/dc/terms/'
openapi:
extensionProperties:
bar: baz
@@ -191,6 +193,8 @@ resources:
hydraContext:
foo:
bar: baz
+ jsonldContext:
+ dct: 'http://purl.org/dc/terms/'
openapi:
extensionProperties:
bar: baz
diff --git a/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php b/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php
index 943790e0ba4..fb4f177d81f 100644
--- a/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php
+++ b/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php
@@ -142,6 +142,9 @@ final class ResourceMetadataCompatibilityTest extends TestCase
'hydraContext' => [
'foo' => ['bar' => 'baz'],
],
+ 'jsonldContext' => [
+ 'dct' => 'http://purl.org/dc/terms/',
+ ],
'openapi' => [
'extensionProperties' => [
'bar' => 'baz',
@@ -356,6 +359,9 @@ final class ResourceMetadataCompatibilityTest extends TestCase
'hydraContext' => [
'foo' => ['bar' => 'baz'],
],
+ 'jsonldContext' => [
+ 'dct' => 'http://purl.org/dc/terms/',
+ ],
'openapi' => [
'extensionProperties' => [
'bar' => 'baz',
@@ -501,6 +507,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase
'schemes',
'cacheHeaders',
'hydraContext',
+ 'jsonldContext',
'openapi',
'paginationViaCursor',
'stateOptions',
diff --git a/src/Metadata/Tests/Extractor/XmlExtractorTest.php b/src/Metadata/Tests/Extractor/XmlExtractorTest.php
index 9dedfc19906..b9d4dc23594 100644
--- a/src/Metadata/Tests/Extractor/XmlExtractorTest.php
+++ b/src/Metadata/Tests/Extractor/XmlExtractorTest.php
@@ -106,6 +106,7 @@ public function testValidXML(): void
'parameters' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
[
'uriTemplate' => '/users/{author}/comments{._format}',
@@ -283,6 +284,7 @@ public function testValidXML(): void
'routeName' => 'custom_route_name',
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
[
'name' => null,
@@ -397,6 +399,7 @@ public function testValidXML(): void
'routeName' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
],
'graphQlOperations' => null,
@@ -410,6 +413,7 @@ public function testValidXML(): void
'parameters' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
],
], $extractor->getResources());
diff --git a/src/Metadata/Tests/Extractor/YamlExtractorTest.php b/src/Metadata/Tests/Extractor/YamlExtractorTest.php
index 6384942192e..7d58abe6ba3 100644
--- a/src/Metadata/Tests/Extractor/YamlExtractorTest.php
+++ b/src/Metadata/Tests/Extractor/YamlExtractorTest.php
@@ -105,6 +105,7 @@ public function testValidYaml(): void
'parameters' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
],
Program::class => [
@@ -178,6 +179,7 @@ public function testValidYaml(): void
'parameters' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
[
'uriTemplate' => '/users/{author}/programs{._format}',
@@ -322,6 +324,7 @@ public function testValidYaml(): void
'parameters' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
[
'name' => null,
@@ -409,6 +412,7 @@ public function testValidYaml(): void
'parameters' => ['author' => new QueryParameter(schema: ['type' => 'string'], required: true, key: 'author', description: 'hello')],
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
],
'graphQlOperations' => null,
@@ -422,6 +426,7 @@ public function testValidYaml(): void
'parameters' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
],
SingleFileConfigDummy::class => [
@@ -495,6 +500,7 @@ public function testValidYaml(): void
'parameters' => null,
'jsonStream' => null,
'map' => null,
+ 'jsonldContext' => null,
],
],
], $extractor->getResources());
diff --git a/tests/Fixtures/TestBundle/ApiResource/JsonLd/JsonLdContextDummy.php b/tests/Fixtures/TestBundle/ApiResource/JsonLd/JsonLdContextDummy.php
index 18cd539f17a..ab545ac6936 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 dd768a9cf55..6906f979db2 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']);
+ }
}