diff --git a/features/jsonld/non_resource.feature b/features/jsonld/non_resource.feature
index 654d90d35bf..fefa71f8609 100644
--- a/features/jsonld/non_resource.feature
+++ b/features/jsonld/non_resource.feature
@@ -38,6 +38,7 @@ Feature: JSON-LD non-resource handling
}
}
"""
+ And the JSON node "notAResource.@id" should not exist
Scenario: Get a resource containing a raw object with selected properties
Given there are 1 dummy objects with relatedDummy and its thirdLevel
@@ -123,3 +124,11 @@ Feature: JSON-LD non-resource handling
"id": 1
}
"""
+
+ @php8
+ Scenario: Get a generated id
+ When I send a "GET" request to "/genids/1"
+ 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 node "totalPrice.@id" should exist
diff --git a/src/Hydra/Serializer/CollectionNormalizer.php b/src/Hydra/Serializer/CollectionNormalizer.php
index 91a2903edca..af5791decb6 100644
--- a/src/Hydra/Serializer/CollectionNormalizer.php
+++ b/src/Hydra/Serializer/CollectionNormalizer.php
@@ -87,6 +87,7 @@ public function normalize($object, $format = null, array $context = []): array
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']);
$context = $this->initContext($resourceClass, $context);
+ $context['api_collection_sub_level'] = true;
$data = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context);
if ($this->iriConverter instanceof LegacyIriConverterInterface) {
diff --git a/src/JsonLd/ContextBuilder.php b/src/JsonLd/ContextBuilder.php
index ea04f293d3f..c7e2e3c3608 100644
--- a/src/JsonLd/ContextBuilder.php
+++ b/src/JsonLd/ContextBuilder.php
@@ -200,6 +200,14 @@ public function getAnonymousResourceContext($object, array $context = [], int $r
}
}
+ if ($this->iriConverter && isset($context['gen_id']) && true === $context['gen_id']) {
+ $jsonLdContext['@id'] = $this->iriConverter->getIriFromResource($object);
+ }
+
+ if (false === ($context['iri'] ?? null)) {
+ trigger_deprecation('api-platform/core', '2.7', 'An anonymous resource will use a Skolem IRI in API Platform 3.0. Use #[ApiProperty(genId: false)] to keep this behavior in 3.0.');
+ }
+
if ($context['has_context'] ?? false) {
unset($jsonLdContext['@context']);
}
diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php
index 8482f236a5b..1102f7b48ba 100644
--- a/src/JsonLd/Serializer/ItemNormalizer.php
+++ b/src/JsonLd/Serializer/ItemNormalizer.php
@@ -89,6 +89,12 @@ public function normalize($object, $format = null, array $context = [])
$context = $this->initContext($resourceClass, $context);
$metadata = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context);
} elseif ($this->contextBuilder instanceof AnonymousContextBuilderInterface) {
+ if ($context['api_collection_sub_level'] ?? false) {
+ unset($context['api_collection_sub_level']);
+ $context['output']['genid'] = true;
+ $context['output']['iri'] = null;
+ }
+
// We should improve what's behind the context creation, its probably more complicated then it should
$metadata = $this->createJsonLdContext($this->contextBuilder, $object, $context);
}
diff --git a/src/JsonLd/Serializer/JsonLdContextTrait.php b/src/JsonLd/Serializer/JsonLdContextTrait.php
index 296c6476143..43612ccc785 100644
--- a/src/JsonLd/Serializer/JsonLdContextTrait.php
+++ b/src/JsonLd/Serializer/JsonLdContextTrait.php
@@ -51,7 +51,7 @@ private function createJsonLdContext(AnonymousContextBuilderInterface $contextBu
{
// We're in a collection, don't add the @context part
if (isset($context['jsonld_has_context'])) {
- return $contextBuilder->getAnonymousResourceContext($object, ($context['output'] ?? []) + ['api_resource' => $context['api_resource'] ?? null, 'has_context' => true]);
+ return $contextBuilder->getAnonymousResourceContext($object, ($context['output'] ?? []) + ['api_resource' => $context['api_resource'] ?? null, 'has_context' => true, 'iri' => false]);
}
$context['jsonld_has_context'] = true;
diff --git a/src/Metadata/ApiProperty.php b/src/Metadata/ApiProperty.php
index a9641a931c1..cd62a5656ca 100644
--- a/src/Metadata/ApiProperty.php
+++ b/src/Metadata/ApiProperty.php
@@ -92,6 +92,7 @@ final class ApiProperty
private $schema;
private $initializable;
+ private $genId;
/**
* @var string[]
@@ -146,6 +147,7 @@ public function __construct(
?array $builtinTypes = null,
?array $schema = null,
?bool $initializable = null,
+ ?bool $genId = null,
$iris = null,
@@ -175,6 +177,7 @@ public function __construct(
$this->builtinTypes = $builtinTypes;
$this->schema = $schema;
$this->initializable = $initializable;
+ $this->genId = $genId;
$this->iris = $iris;
$this->extraProperties = $extraProperties;
}
@@ -507,4 +510,20 @@ public function withIris($iris): self
return $metadata;
}
+
+ /**
+ * Whether to generate a skolem iri on anonymous resources.
+ */
+ public function getGenId()
+ {
+ return $this->genId;
+ }
+
+ public function withGenId(bool $genId): self
+ {
+ $metadata = clone $this;
+ $metadata->genId = $genId;
+
+ return $metadata;
+ }
}
diff --git a/src/Metadata/Extractor/XmlPropertyExtractor.php b/src/Metadata/Extractor/XmlPropertyExtractor.php
index f5fcabf023c..2da5d1dd5dc 100644
--- a/src/Metadata/Extractor/XmlPropertyExtractor.php
+++ b/src/Metadata/Extractor/XmlPropertyExtractor.php
@@ -71,6 +71,7 @@ protected function extractPath(string $path)
'initializable' => $this->phpize($property, 'initializable', 'bool'),
'extraProperties' => $this->buildExtraProperties($property, 'extraProperties'),
'iris' => $this->buildArrayValue($property, 'iri'),
+ 'genId' => $this->phpize($property, 'genId', 'bool'),
];
}
}
diff --git a/src/Metadata/Extractor/YamlPropertyExtractor.php b/src/Metadata/Extractor/YamlPropertyExtractor.php
index ca14895a0e6..dd1231ca2aa 100644
--- a/src/Metadata/Extractor/YamlPropertyExtractor.php
+++ b/src/Metadata/Extractor/YamlPropertyExtractor.php
@@ -92,6 +92,7 @@ private function buildProperties(array $resourcesYaml): void
'example' => $propertyValues['example'] ?? null,
'builtinTypes' => $this->buildAttribute($propertyValues, 'builtinTypes'),
'schema' => $this->buildAttribute($propertyValues, 'schema'),
+ 'genId' => $this->phpize($propertyValues, 'genId', 'bool'),
];
}
}
diff --git a/src/Metadata/Extractor/schema/properties.xsd b/src/Metadata/Extractor/schema/properties.xsd
index 86422068b7f..f25266eba22 100644
--- a/src/Metadata/Extractor/schema/properties.xsd
+++ b/src/Metadata/Extractor/schema/properties.xsd
@@ -43,6 +43,7 @@
+
diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php
index 279edf8fd3a..3fc9b19d7c0 100644
--- a/src/Serializer/AbstractItemNormalizer.php
+++ b/src/Serializer/AbstractItemNormalizer.php
@@ -831,11 +831,9 @@ protected function getAttributeValue($object, $attribute, $format = null, array
unset($childContext['iri'], $childContext['uri_variables']);
if ($propertyMetadata instanceof PropertyMetadata) {
- $childContext['output']['iri'] = $propertyMetadata->getIri();
+ $childContext['output']['iri'] = $propertyMetadata->getIri() ?? false;
} else {
- if (null !== ($propertyIris = $propertyMetadata->getIris())) {
- $childContext['output']['iri'] = 1 === \count($propertyIris) ? $propertyIris[0] : $propertyIris;
- }
+ $childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? false;
}
return $this->serializer->normalize($attributeValue, $format, $childContext);
diff --git a/tests/Fixtures/TestBundle/Model/GenId.php b/tests/Fixtures/TestBundle/Model/GenId.php
new file mode 100644
index 00000000000..58f40916dfd
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Model/GenId.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Model;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Operation;
+
+#[Get('/genids/{id}', provider: [GenId::class, 'getData'])]
+class GenId
+{
+ #[ApiProperty(genId: true)]
+ public MonetaryAmount $totalPrice;
+
+ public function __construct(public int $id)
+ {
+ $this->totalPrice = new MonetaryAmount(1000.01);
+ }
+
+ public static function getData(Operation $operation, array $uriVariables = [], array $context = []): self
+ {
+ return new self($uriVariables['id']);
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Model/MonetaryAmount.php b/tests/Fixtures/TestBundle/Model/MonetaryAmount.php
new file mode 100644
index 00000000000..52adef125f1
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Model/MonetaryAmount.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Model;
+
+class MonetaryAmount
+{
+ public function __construct(public float $value = 0.0, public string $currency = 'EUR', public float $minValue = 0.0)
+ {
+ }
+}
diff --git a/tests/Hydra/Serializer/CollectionNormalizerTest.php b/tests/Hydra/Serializer/CollectionNormalizerTest.php
index b394f295ec8..428fd791ba7 100644
--- a/tests/Hydra/Serializer/CollectionNormalizerTest.php
+++ b/tests/Hydra/Serializer/CollectionNormalizerTest.php
@@ -327,6 +327,7 @@ private function normalizePaginator($partial = false)
'jsonld_has_context' => true,
'api_sub_level' => true,
'resource_class' => 'Foo',
+ 'api_collection_sub_level' => true,
])->willReturn(['name' => 'Kévin', 'friend' => 'Smail']);
$normalizer = new CollectionNormalizer($contextBuilder->reveal(), $resourceClassResolverProphecy->reveal(), $iriConvert->reveal());
diff --git a/tests/Metadata/Extractor/Adapter/XmlPropertyAdapter.php b/tests/Metadata/Extractor/Adapter/XmlPropertyAdapter.php
index 52248aed1ad..9aee0b5896f 100644
--- a/tests/Metadata/Extractor/Adapter/XmlPropertyAdapter.php
+++ b/tests/Metadata/Extractor/Adapter/XmlPropertyAdapter.php
@@ -42,6 +42,7 @@ final class XmlPropertyAdapter implements PropertyAdapterInterface
'securityPostDenormalize',
'initializable',
'iris',
+ 'genId',
];
/**
diff --git a/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php b/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php
index 84677a9912d..e310c802468 100644
--- a/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php
+++ b/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php
@@ -75,6 +75,7 @@ final class PropertyMetadataCompatibilityTest extends TestCase
'custom_property' => 'Lorem ipsum dolor sit amet',
],
'iris' => ['https://schema.org/totalPrice'],
+ 'genId' => true,
];
/**