Skip to content

Commit 4256fa0

Browse files
authored
fix(json-ld): use a Skolem IRI instead of blank nodes (#4731)
1 parent a5a86e0 commit 4256fa0

File tree

10 files changed

+75
-196
lines changed

10 files changed

+75
-196
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 2.7.0-alpha.3
4+
5+
* Implements Skolem IRIs instead of blank nodes, can be disabled using `iri: false` (#4731)
6+
37
## 2.7.0-alpha.2
48

59
* Review interfaces (ProcessorInterface, ProviderInterface, TypeConverterInterface, ResolverFactoryInterface etc.) to use `ApiPlatform\Metadata\Operation` instead of `operationName` (#4712)

src/JsonLd/ContextBuilder.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,12 @@ public function getAnonymousResourceContext($object, array $context = [], int $r
185185
$shortName
186186
),
187187
'@type' => $shortName,
188-
'@id' => $context['iri'] ?? '_:'.(\function_exists('spl_object_id') ? spl_object_id($object) : spl_object_hash($object)),
189188
];
190189

190+
if (!isset($context['iri']) || false !== $context['iri']) {
191+
$jsonLdContext['@id'] = $context['iri'] ?? '/.well-known/genid/'.(bin2hex(random_bytes(10)));
192+
}
193+
191194
if ($context['has_context'] ?? false) {
192195
unset($jsonLdContext['@context']);
193196
}

src/Metadata/ApiProperty.php

Lines changed: 24 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313

1414
namespace ApiPlatform\Metadata;
1515

16-
use ApiPlatform\Core\Metadata\Property\SubresourceMetadata;
17-
use ApiPlatform\Metadata\Property\DeprecationMetadataTrait;
1816
use Symfony\Component\PropertyInfo\Type;
19-
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
2017

2118
/**
2219
* ApiProperty annotation.
@@ -26,8 +23,6 @@
2623
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)]
2724
final class ApiProperty
2825
{
29-
use DeprecationMetadataTrait;
30-
3126
/**
3227
* @var string
3328
*/
@@ -96,27 +91,29 @@ final class ApiProperty
9691

9792
private $schema;
9893
private $initializable;
94+
private $iri;
9995

10096
/**
10197
* @var array
10298
*/
10399
private $extraProperties;
104100

105101
/**
106-
* @param bool|null $readableLink https://api-platform.com/docs/core/serialization/#force-iri-with-relations-of-the-same-type-parentchilds-relations
107-
* @param bool|null $writableLink https://api-platform.com/docs/core/serialization/#force-iri-with-relations-of-the-same-type-parentchilds-relations
108-
* @param bool|null $required https://api-platform.com/docs/admin/validation/#client-side-validation
109-
* @param bool|null $identifier https://api-platform.com/docs/core/identifiers/
110-
* @param string|null $default
111-
* @param string|null $example https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
112-
* @param string|null $deprecationReason https://api-platform.com/docs/core/deprecations/#deprecating-resource-classes-operations-and-properties
113-
* @param bool|null $fetchEager https://api-platform.com/docs/core/performance/#eager-loading
114-
* @param array|null $jsonldContext https://api-platform.com/docs/core/extending-jsonld-context/#extending-json-ld-and-hydra-contexts
115-
* @param array|null $openapiContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
116-
* @param bool|null $push https://api-platform.com/docs/core/push-relations/
117-
* @param string|null $security https://api-platform.com/docs/core/security
118-
* @param string|null $securityPostDenormalize https://api-platform.com/docs/core/security/#executing-access-control-rules-after-denormalization
119-
* @param array|null $types the RDF types of this property
102+
* @param bool|null $readableLink https://api-platform.com/docs/core/serialization/#force-iri-with-relations-of-the-same-type-parentchilds-relations
103+
* @param bool|null $writableLink https://api-platform.com/docs/core/serialization/#force-iri-with-relations-of-the-same-type-parentchilds-relations
104+
* @param bool|null $required https://api-platform.com/docs/admin/validation/#client-side-validation
105+
* @param bool|null $identifier https://api-platform.com/docs/core/identifiers/
106+
* @param string|null $default
107+
* @param string|null $example https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
108+
* @param string|null $deprecationReason https://api-platform.com/docs/core/deprecations/#deprecating-resource-classes-operations-and-properties
109+
* @param bool|null $fetchEager https://api-platform.com/docs/core/performance/#eager-loading
110+
* @param array|null $jsonldContext https://api-platform.com/docs/core/extending-jsonld-context/#extending-json-ld-and-hydra-contexts
111+
* @param array|null $openapiContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
112+
* @param bool|null $push https://api-platform.com/docs/core/push-relations/
113+
* @param string|null $security https://api-platform.com/docs/core/security
114+
* @param string|null $securityPostDenormalize https://api-platform.com/docs/core/security/#executing-access-control-rules-after-denormalization
115+
* @param array|null $types the RDF types of this property
116+
* @param bool|string|null $iri the IRI representing the property, can be `false` to remove the generated "@id"
120117
*/
121118
public function __construct(
122119
?string $description = null,
@@ -144,6 +141,8 @@ public function __construct(
144141
?array $schema = null,
145142
?bool $initializable = null,
146143

144+
$iri = null,
145+
147146
// attributes
148147
array $extraProperties = []
149148
) {
@@ -169,6 +168,7 @@ public function __construct(
169168
$this->builtinTypes = $builtinTypes;
170169
$this->schema = $schema;
171170
$this->initializable = $initializable;
171+
$this->iri = $iri;
172172
$this->extraProperties = $extraProperties;
173173
}
174174

@@ -467,169 +467,23 @@ public function withExtraProperties(array $extraProperties = []): self
467467
return $self;
468468
}
469469

470-
/**
471-
* @deprecated since 2.7, to be removed in 3.0
472-
*/
473-
public function withSubresource(SubresourceMetadata $subresourceMetadata): self
474-
{
475-
trigger_deprecation('api-platform/core', '2.7', 'Declaring a subresource on a property is deprecated, use alternate URLs instead.');
476-
$self = clone $this;
477-
$self->extraProperties['subresource'] = $subresourceMetadata;
478-
479-
return $self;
480-
}
481-
482-
/**
483-
* @deprecated since 2.7, to be removed in 3.0
484-
*/
485-
public function getSubresource(): ?SubresourceMetadata
486-
{
487-
return $this->extraProperties['subresource'] ?? null;
488-
}
489-
490-
/**
491-
* Represents whether the property has a subresource.
492-
*
493-
* @deprecated since 2.7, to be removed in 3.0
494-
*/
495-
public function hasSubresource(): bool
496-
{
497-
return isset($this->extraProperties['subresource']);
498-
}
499-
500-
/**
501-
* @deprecated since 2.6, to be removed in 3.0
502-
*/
503-
public function getChildInherited(): ?string
504-
{
505-
return $this->extraProperties['childInherited'] ?? null;
506-
}
507-
508-
/**
509-
* @deprecated since 2.6, to be removed in 3.0
510-
*/
511-
public function hasChildInherited(): bool
512-
{
513-
return isset($this->extraProperties['childInherited']);
514-
}
515-
516-
/**
517-
* @deprecated since 2.4, to be removed in 3.0
518-
*/
519-
public function isChildInherited(): ?string
520-
{
521-
trigger_deprecation('api-platform/core', '2.4', sprintf('"%s::%s" is deprecated since 2.4 and will be removed in 3.0.', __CLASS__, __METHOD__));
522-
523-
return $this->getChildInherited();
524-
}
525-
526-
/**
527-
* @deprecated since 2.6, to be removed in 3.0
528-
*/
529-
public function withChildInherited(string $childInherited): self
530-
{
531-
trigger_deprecation('api-platform/core', '2.6', sprintf('"%s::%s" is deprecated since 2.6 and will be removed in 3.0.', __CLASS__, __METHOD__));
532-
533-
$metadata = clone $this;
534-
$metadata->extraProperties['childInherited'] = $childInherited;
535-
536-
return $metadata;
537-
}
538-
539470
/**
540471
* Gets IRI of this property.
541-
*
542-
* @deprecated since 2.7, to be removed in 3.0, use getTypes instead
543472
*/
544-
public function getIri(): ?string
473+
public function getIri()
545474
{
546-
return $this->types[0] ?? null;
475+
return $this->iri;
547476
}
548477

549478
/**
550479
* Returns a new instance with the given IRI.
551480
*
552-
* @deprecated since 2.7, to be removed in 3.0, use withTypes instead
553-
*/
554-
public function withIri(string $iri = null): self
555-
{
556-
trigger_deprecation('api-platform/core', '2.7', sprintf('"%s::%s" is deprecated since 2.7 and will be removed in 3.0, use Type instead.', __CLASS__, __METHOD__));
557-
558-
$metadata = clone $this;
559-
$metadata->types = [$iri];
560-
561-
return $metadata;
562-
}
563-
564-
/**
565-
* Gets an attribute.
566-
*
567-
* @deprecated since 2.7, to be removed in 3.0, use getExtraProperties instead
568-
*
569-
* @param mixed|null $defaultValue
481+
* @param mixed $iri
570482
*/
571-
public function getAttribute(string $key, $defaultValue = null)
483+
public function withIri($iri): self
572484
{
573-
trigger_deprecation('api-platform/core', '2.7', sprintf('"%s::%s" is deprecated since 2.7 and will be removed in 3.0.', __CLASS__, __METHOD__));
574-
575-
if (!$this->camelCaseToSnakeCaseNameConverter) {
576-
$this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();
577-
}
578-
579-
$propertyName = $this->camelCaseToSnakeCaseNameConverter->denormalize($key);
580-
581-
if (isset($this->{$propertyName})) {
582-
return $this->{$propertyName} ?? $defaultValue;
583-
}
584-
585-
return $this->extraProperties[$key] ?? $defaultValue;
586-
}
587-
588-
/**
589-
* Gets attributes.
590-
*
591-
* @deprecated since 2.7, to be removed in 3.0, renamed as getExtraProperties
592-
*/
593-
public function getAttributes(): ?array
594-
{
595-
return $this->extraProperties;
596-
}
597-
598-
/**
599-
* Returns a new instance with the given attribute.
600-
*
601-
* @deprecated since 2.7, to be removed in 3.0, renamed as withExtraProperties
602-
*/
603-
public function withAttributes(array $attributes): self
604-
{
605-
trigger_deprecation('api-platform/core', '2.7', sprintf('"%s::%s" is deprecated since 2.7 and will be removed in 3.0.', __CLASS__, __METHOD__));
606-
607-
$metadata = clone $this;
608-
609-
return $this->withDeprecatedAttributes($metadata, $attributes);
610-
}
611-
612-
/**
613-
* Gets type.
614-
*
615-
* @deprecated since 2.7, to be removed in 3.0, renamed as getBuiltinTypes
616-
*/
617-
public function getType(): ?Type
618-
{
619-
return $this->builtinTypes[0] ?? null;
620-
}
621-
622-
/**
623-
* Returns a new instance with the given type.
624-
*
625-
* @deprecated since 2.7, to be removed in 3.0, renamed as withBuiltinTypes
626-
*/
627-
public function withType(Type $type): self
628-
{
629-
trigger_deprecation('api-platform/core', '2.7', sprintf('"%s::%s" is deprecated since 2.7 and will be removed in 3.0, use builtinTypes instead.', __CLASS__, __METHOD__));
630-
631485
$metadata = clone $this;
632-
$metadata->builtinTypes = [$type];
486+
$metadata->iri = $iri;
633487

634488
return $metadata;
635489
}

src/Metadata/Extractor/schema/properties.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<xsd:attribute name="security" type="xsd:string"/>
4242
<xsd:attribute name="securityPostDenormalize" type="xsd:string"/>
4343
<xsd:attribute name="initializable" type="xsd:boolean"/>
44+
<xsd:attribute name="iri" type="xsd:string"/>
4445
</xsd:complexType>
4546

4647
<xsd:complexType name="types">

src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ private function createMetadata(ApiProperty $attribute, ApiProperty $propertyMet
105105
'getAttribute' !== $method &&
106106
'isChildInherited' !== $method &&
107107
'getSubresource' !== $method &&
108-
'getIri' !== $method &&
109108
'getAttributes' !== $method &&
110109
// end of deprecated methods
111110

src/Serializer/AbstractItemNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,10 @@ protected function getAttributeValue($object, $attribute, $format = null, array
779779
$childContext = $this->createChildContext($context, $attribute, $format);
780780
unset($childContext['iri']);
781781

782+
if (null !== ($propertyIri = $propertyMetadata->getIri())) {
783+
$childContext['output']['iri'] = $propertyIri;
784+
}
785+
782786
return $this->serializer->normalize($attributeValue, $format, $childContext);
783787
}
784788

tests/Hydra/Serializer/DocumentationNormalizerTest.php

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use ApiPlatform\Api\ResourceClassResolverInterface;
1717
use ApiPlatform\Api\UrlGeneratorInterface;
1818
use ApiPlatform\Core\Api\OperationMethodResolverInterface;
19+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface;
20+
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
1921
use ApiPlatform\Core\Metadata\Property\SubresourceMetadata;
2022
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2123
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
@@ -103,22 +105,36 @@ private function doTestNormalize(OperationMethodResolverInterface $operationMeth
103105
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
104106
$propertyNameCollectionFactoryProphecy->create('dummy', [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name', 'description', 'nameConverted', 'relatedDummy']));
105107

106-
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
107-
$propertyMetadataFactoryProphecy->create('dummy', 'name', Argument::type('array'))->shouldBeCalled()->willReturn(
108-
(new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('name')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)
109-
);
110-
$propertyMetadataFactoryProphecy->create('dummy', 'description', Argument::type('array'))->shouldBeCalled()->willReturn(
111-
(new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('description')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@type' => '@id'])
112-
);
113-
$propertyMetadataFactoryProphecy->create('dummy', 'nameConverted', Argument::type('array'))->shouldBeCalled()->willReturn(
114-
(new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('name converted')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)
115-
);
116-
$propertyMetadataFactoryProphecy->create('dummy', 'relatedDummy', Argument::type('array'))->shouldBeCalled()->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'dummy', true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, 'relatedDummy'))])->withDescription('This is a name.')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true));
108+
if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) {
109+
$propertyMetadataFactoryProphecy = $this->prophesize(LegacyPropertyMetadataFactoryInterface::class);
110+
$propertyMetadataFactoryProphecy->create('dummy', 'name', Argument::type('array'))->shouldBeCalled()->willReturn(
111+
(new PropertyMetadata())->withType(new Type(Type::BUILTIN_TYPE_STRING))->withDescription('name')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)
112+
);
113+
$propertyMetadataFactoryProphecy->create('dummy', 'description', Argument::type('array'))->shouldBeCalled()->willReturn(
114+
(new PropertyMetadata())->withType(new Type(Type::BUILTIN_TYPE_STRING))->withDescription('description')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withAttributes(['jsonld_context' => ['@type' => '@id']])
115+
);
116+
$propertyMetadataFactoryProphecy->create('dummy', 'nameConverted', Argument::type('array'))->shouldBeCalled()->willReturn(
117+
(new PropertyMetadata())->withType(new Type(Type::BUILTIN_TYPE_STRING))->withDescription('name converted')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)
118+
);
119+
$propertyMetadataFactoryProphecy->create('dummy', 'relatedDummy', Argument::type('array'))->shouldBeCalled()->willReturn((new PropertyMetadata())->withType(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'dummy', true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, 'relatedDummy')))->withDescription('This is a name.')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true));
120+
} else {
121+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
122+
$propertyMetadataFactoryProphecy->create('dummy', 'name', Argument::type('array'))->shouldBeCalled()->willReturn(
123+
(new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('name')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)
124+
);
125+
$propertyMetadataFactoryProphecy->create('dummy', 'description', Argument::type('array'))->shouldBeCalled()->willReturn(
126+
(new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('description')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@type' => '@id'])
127+
);
128+
$propertyMetadataFactoryProphecy->create('dummy', 'nameConverted', Argument::type('array'))->shouldBeCalled()->willReturn(
129+
(new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('name converted')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)
130+
);
131+
$propertyMetadataFactoryProphecy->create('dummy', 'relatedDummy', Argument::type('array'))->shouldBeCalled()->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'dummy', true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, 'relatedDummy'))])->withDescription('This is a name.')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true));
132+
}
117133

118134
$subresourceOperationFactoryProphecy = null;
119135
if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) {
120136
$subresourceMetadata = new SubresourceMetadata('relatedDummy', false);
121-
$propertyMetadataFactoryProphecy->create('dummy', 'relatedDummy')->shouldBeCalled()->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'dummy', true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, 'relatedDummy'))])->withDescription('This is a name.')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withSubresource($subresourceMetadata));
137+
$propertyMetadataFactoryProphecy->create('dummy', 'relatedDummy')->shouldBeCalled()->willReturn((new PropertyMetadata())->withType(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'dummy', true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, 'relatedDummy')))->withDescription('This is a name.')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withSubresource($subresourceMetadata));
122138
$subresourceOperationFactoryProphecy = $this->prophesize(SubresourceOperationFactoryInterface::class);
123139
$subresourceOperationFactoryProphecy->create('dummy')->shouldBeCalled()->willReturn([
124140
'api_dummies_subresource_get_related_dummy' => [

0 commit comments

Comments
 (0)