Skip to content

Commit 60240f5

Browse files
authored
feat: improve iri converter interface (#4734)
* feat: improve iri converter interface * fix bc layer * fix new metadata iris * item non nullable * fix phpstan * self review * fix features * iterable * review interface * phpstan
1 parent 8e874f0 commit 60240f5

File tree

54 files changed

+454
-290
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+454
-290
lines changed

.github/patch/v3_features.patch

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
diff --git a/features/jsonld/input_output.feature b/features/jsonld/input_output.feature
2+
index ad7ee0344..f05fef8bf 100644
3+
--- a/features/jsonld/input_output.feature
4+
+++ b/features/jsonld/input_output.feature
5+
@@ -65,7 +65,7 @@ Feature: JSON-LD DTO input and output
6+
"""
7+
{
8+
"@context": "/contexts/DummyDtoCustom",
9+
- "@id": "/dummy_dto_customs",
10+
+ "@id": "/dummy_dto_custom_output",
11+
"@type": "hydra:Collection",
12+
"hydra:member": [
13+
{
14+
diff --git a/features/main/custom_identifier_with_subresource.feature b/features/main/custom_identifier_with_subresource.feature
15+
index cf18a15a3..773dd8728 100644
16+
--- a/features/main/custom_identifier_with_subresource.feature
17+
+++ b/features/main/custom_identifier_with_subresource.feature
18+
@@ -84,7 +84,7 @@ Feature: Using custom parent identifier for subresources
19+
"""
20+
{
21+
"@context": "/contexts/SlugParentDummy",
22+
- "@id": "/slug_parent_dummies/parent-dummy",
23+
+ "@id": "/slug_child_dummies/child-dummy/parent_dummy",
24+
"@type": "SlugParentDummy",
25+
"id": 1,
26+
"slug": "parent-dummy",
27+
diff --git a/features/main/custom_operation.feature b/features/main/custom_operation.feature
28+
index 6b8e4a7bc..9fa829ec5 100644
29+
--- a/features/main/custom_operation.feature
30+
+++ b/features/main/custom_operation.feature
31+
@@ -64,7 +64,7 @@ Feature: Custom operation
32+
"""
33+
{
34+
"@context": "/contexts/CustomActionDummy",
35+
- "@id": "/custom_action_dummies/1",
36+
+ "@id": "/custom_action_collection_dummies/1",
37+
"@type": "CustomActionDummy",
38+
"id": 1,
39+
"foo": "custom!"
40+
diff --git a/features/main/default_order.feature b/features/main/default_order.feature
41+
index 1bc97ab37..912110143 100644
42+
--- a/features/main/default_order.feature
43+
+++ b/features/main/default_order.feature
44+
@@ -127,7 +127,7 @@ Feature: Default order
45+
"""
46+
{
47+
"@context": "/contexts/Foo",
48+
- "@id": "/foos",
49+
+ "@id": "/custom_collection_asc_foos",
50+
"@type": "hydra:Collection",
51+
"hydra:member": [
52+
{
53+
@@ -183,7 +183,7 @@ Feature: Default order
54+
"""
55+
{
56+
"@context": "/contexts/Foo",
57+
- "@id": "/foos",
58+
+ "@id": "/custom_collection_desc_foos",
59+
"@type": "hydra:Collection",
60+
"hydra:member": [
61+
{
62+
diff --git a/features/main/operation.feature b/features/main/operation.feature
63+
index 24dda9b87..8786f6b04 100644
64+
--- a/features/main/operation.feature
65+
+++ b/features/main/operation.feature
66+
@@ -47,7 +47,7 @@ Feature: Operation support
67+
"""
68+
{
69+
"@context": "/contexts/EmbeddedDummy",
70+
- "@id": "/embedded_dummies/1",
71+
+ "@id": "/embedded_dummies_groups/1",
72+
"@type": "EmbeddedDummy",
73+
"name": "Dummy #1",
74+
"embeddedDummy": {
75+
@@ -76,7 +76,7 @@ Feature: Operation support
76+
"""
77+
{
78+
"@context": "/contexts/Book",
79+
- "@id": "/books/1",
80+
+ "@id": "/books/by_isbn/9780451524935",
81+
"@type": "Book",
82+
"name": "1984",
83+
"isbn": "9780451524935",
84+
diff --git a/features/main/subresource.feature b/features/main/subresource.feature
85+
index ae4969a5a..2fd278ea0 100644
86+
--- a/features/main/subresource.feature
87+
+++ b/features/main/subresource.feature
88+
@@ -13,7 +13,7 @@ Feature: Subresource support
89+
"""
90+
{
91+
"@context": "/contexts/Answer",
92+
- "@id": "/answers/1",
93+
+ "@id": "/questions/1/answer",
94+
"@type": "Answer",
95+
"id": 1,
96+
"content": "42",
97+
@@ -234,7 +234,7 @@ Feature: Subresource support
98+
"""
99+
{
100+
"@context": "/contexts/RelatedDummy",
101+
- "@id": "/related_dummies/2",
102+
+ "@id": "/dummies/1/related_dummies/2",
103+
"@type": "https://schema.org/Product",
104+
"id": 2,
105+
"name": null,
106+
@@ -274,7 +274,7 @@ Feature: Subresource support
107+
"""
108+
{
109+
"@context": "/contexts/ThirdLevel",
110+
- "@id": "/third_levels/1",
111+
+ "@id": "/dummies/1/related_dummies/1/third_level",
112+
"@type": "ThirdLevel",
113+
"fourthLevel": "/fourth_levels/1",
114+
"badFourthLevel": null,
115+
@@ -293,7 +293,7 @@ Feature: Subresource support
116+
"""
117+
{
118+
"@context": "/contexts/FourthLevel",
119+
- "@id": "/fourth_levels/1",
120+
+ "@id": "/dummies/1/related_dummies/1/third_level/fourth_level",
121+
"@type": "FourthLevel",
122+
"badThirdLevel": [],
123+
"id": 1,
124+
@@ -411,7 +411,7 @@ Feature: Subresource support
125+
"""
126+
{
127+
"@context": "/contexts/Dummy",
128+
- "@id": "/dummies/1",
129+
+ "@id": "/related_owned_dummies/1/owning_dummy",
130+
"@type": "Dummy",
131+
"description": null,
132+
"dummy": null,
133+
@@ -444,7 +444,7 @@ Feature: Subresource support
134+
"""
135+
{
136+
"@context": "/contexts/Dummy",
137+
- "@id": "/dummies/1",
138+
+ "@id": "/related_owning_dummies/1/owned_dummy",
139+
"@type": "Dummy",
140+
"description": null,
141+
"dummy": null,

.github/workflows/ci.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,8 +818,11 @@ jobs:
818818
- name: Convert metadata to API Platform 3
819819
run: |
820820
tests/Fixtures/app/console api:upgrade-resource -f
821-
- name: Clear test app cache
822-
run: rm -Rf tests/Fixtures/app/var/cache/*
821+
- name: Apply behat features patch (IRIs update)
822+
run: |
823+
git apply .github/patch/v3_features.patch
824+
- name: clear test app cache
825+
run: rm -rf tests/Fixtures/app/var/cache/*
823826
- name: Run Behat tests
824827
run: |
825828
mkdir -p build/logs/behat
@@ -901,6 +904,9 @@ jobs:
901904
- name: Convert metadata to API Platform 3
902905
run: |
903906
tests/Fixtures/app/console api:upgrade-resource -f
907+
- name: Apply behat features patch (IRIs update)
908+
run: |
909+
git apply .github/patch/v3_features.patch
904910
- name: Clear test app cache
905911
run: rm -Rf tests/Fixtures/app/var/cache/*
906912
- name: Run Behat tests

features/main/crud_uri_variables.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ Feature: Uri Variables
169169
"""
170170
{
171171
"@context": "/contexts/Company",
172-
"@id": "/companies/1",
172+
"@id": "/employees/1/company",
173173
"@type": "Company",
174174
"id": 1,
175175
"name": "Foo Company 1",

src/Api/IdentifiersExtractor.php

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
use ApiPlatform\Exception\RuntimeException;
1818
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
1919
use ApiPlatform\Metadata\HttpOperation;
20+
use ApiPlatform\Metadata\Operation;
2021
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2122
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2223
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2324
use ApiPlatform\Util\ResourceClassInfoTrait;
25+
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
2426
use Symfony\Component\PropertyAccess\PropertyAccess;
2527
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
2628

@@ -51,11 +53,11 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource
5153
*
5254
* TODO: 3.0 identifiers should be stringable?
5355
*/
54-
public function getIdentifiersFromItem($item, string $operationName = null, array $context = []): array
56+
public function getIdentifiersFromItem($item, Operation $operation = null, array $context = []): array
5557
{
5658
$identifiers = [];
5759
$resourceClass = $this->getResourceClass($item, true);
58-
$operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName, false, true);
60+
$operation = $operation ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation(null, false, true);
5961

6062
if ($operation instanceof HttpOperation) {
6163
$links = $operation->getUriVariables();
@@ -100,16 +102,20 @@ private function getIdentifierValue($item, string $class, string $property, stri
100102
continue;
101103
}
102104

103-
if ($type->isCollection()) {
104-
$collectionValueType = $type->getCollectionValueTypes()[0] ?? null;
105+
try {
106+
if ($type->isCollection()) {
107+
$collectionValueType = $type->getCollectionValueTypes()[0] ?? null;
105108

106-
if (null !== $collectionValueType && $collectionValueType->getClassName() === $class) {
107-
return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, sprintf('%s[0].%s', $propertyName, $property)), $parameterName);
109+
if (null !== $collectionValueType && $collectionValueType->getClassName() === $class) {
110+
return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, sprintf('%s[0].%s', $propertyName, $property)), $parameterName);
111+
}
108112
}
109-
}
110113

111-
if ($type->getClassName() === $class) {
112-
return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, "$propertyName.$property"), $parameterName);
114+
if ($type->getClassName() === $class) {
115+
return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, "$propertyName.$property"), $parameterName);
116+
}
117+
} catch (NoSuchPropertyException $e) {
118+
throw new RuntimeException('Not able to retrieve identifiers.', $e->getCode(), $e);
113119
}
114120
}
115121

src/Api/IdentifiersExtractorInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Api;
1515

1616
use ApiPlatform\Exception\RuntimeException;
17+
use ApiPlatform\Metadata\Operation;
1718

1819
/**
1920
* Extracts identifiers for a given Resource according to the retrieved Metadata.
@@ -29,5 +30,5 @@ interface IdentifiersExtractorInterface
2930
*
3031
* @throws RuntimeException
3132
*/
32-
public function getIdentifiersFromItem($item, ?string $operationName = null, array $context = []): array;
33+
public function getIdentifiersFromItem($item, ?Operation $operation = null, array $context = []): array;
3334
}

src/Api/IriConverterInterface.php

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Exception\InvalidArgumentException;
1717
use ApiPlatform\Exception\ItemNotFoundException;
1818
use ApiPlatform\Exception\RuntimeException;
19+
use ApiPlatform\Metadata\Operation;
1920

2021
/**
2122
* Converts item and resources to IRI and vice versa.
@@ -32,22 +33,15 @@ interface IriConverterInterface
3233
*
3334
* @return object
3435
*/
35-
public function getItemFromIri(string $iri, array $context = []);
36+
public function getResourceFromIri(string $iri, array $context = [], ?Operation $operation = null);
3637

3738
/**
3839
* Gets the IRI associated with the given item.
3940
*
40-
* @param object $item
41+
* @param object|class-string $resource
4142
*
4243
* @throws InvalidArgumentException
4344
* @throws RuntimeException
4445
*/
45-
public function getIriFromItem($item, string $operationName = null, int $referenceType = UrlGeneratorInterface::ABS_PATH, array $context = []): string;
46-
47-
/**
48-
* Gets the IRI associated with the given resource collection.
49-
*
50-
* @throws InvalidArgumentException
51-
*/
52-
public function getIriFromResourceClass(string $resourceClass, string $operationName = null, int $referenceType = UrlGeneratorInterface::ABS_PATH, array $context = []): string;
46+
public function getIriFromResource($resource, int $referenceType = UrlGeneratorInterface::ABS_PATH, ?Operation $operation = null, array $context = []): ?string;
5347
}

src/Core/EventListener/WriteListener.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace ApiPlatform\Core\EventListener;
1515

1616
use ApiPlatform\Api\IriConverterInterface;
17-
use ApiPlatform\Core\Api\IriConverterInterface as IriConverterLegacyInterface;
17+
use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface;
1818
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
1919
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
2020
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
@@ -43,7 +43,7 @@ final class WriteListener
4343
public const OPERATION_ATTRIBUTE_KEY = 'write';
4444

4545
private $dataPersister;
46-
/** @var IriConverterLegacyInterface|IriConverterInterface|null */
46+
/** @var LegacyIriConverterInterface|IriConverterInterface|null */
4747
private $iriConverter;
4848
private $metadataBackwardCompatibilityLayer;
4949

@@ -121,7 +121,7 @@ public function onKernelView(ViewEvent $event): void
121121
}
122122

123123
if ($this->isResourceClass($this->getObjectClass($controllerResult))) {
124-
$request->attributes->set('_api_write_item_iri', $this->iriConverter->getIriFromItem($controllerResult));
124+
$request->attributes->set('_api_write_item_iri', $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($controllerResult) : $this->iriConverter->getIriFromResource($controllerResult));
125125
}
126126

127127
break;

src/Core/Swagger/Serializer/DocumentationNormalizer.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,6 @@ private function updateGetOperation(bool $v3, \ArrayObject $pathOperation, array
395395

396396
if (
397397
($resourceMetadata->getAttributes()['extra_properties']['is_legacy_subresource'] ?? false) ||
398-
($resourceMetadata->getAttributes()['extra_properties']['legacy_subresource_behavior'] ?? false) ||
399398
($resourceMetadata->getAttributes()['extra_properties']['is_alternate_resource_metadata'] ?? false)) {
400399
// Avoid duplicates parameters when there is a filter on a subresource identifier
401400
$parametersMemory = [];

src/Doctrine/Common/Filter/SearchFilterTrait.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ trait SearchFilterTrait
3030
{
3131
use PropertyHelperTrait;
3232

33+
/**
34+
* @var LegacyIriConverterInterface|IriConverterInterface
35+
*/
3336
protected $iriConverter;
3437
protected $propertyAccessor;
3538
protected $identifiersExtractor;
@@ -124,7 +127,8 @@ abstract protected function normalizePropertyName($property);
124127
protected function getIdFromValue(string $value)
125128
{
126129
try {
127-
$item = $this->getIriConverter()->getItemFromIri($value, ['fetch_data' => false]);
130+
$iriConverter = $this->getIriConverter();
131+
$item = $iriConverter instanceof LegacyIriConverterInterface ? $iriConverter->getItemFromIri($value, ['fetch_data' => false]) : $iriConverter->getResourceFromIri($value, ['fetch_data' => false]); // @phpstan-ignore-line bc-compatibility inside a trait
128132

129133
return $this->getPropertyAccessor()->getValue($item, 'id');
130134
} catch (InvalidArgumentException $e) {

src/Doctrine/EventListener/PublishMercureUpdatesListener.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public function __construct(ResourceClassResolverInterface $resourceClassResolve
100100
new ExpressionFunction('iri', static function (string $apiResource, int $referenceType = UrlGeneratorInterface::ABS_URL): string {
101101
return sprintf('iri(%s, %d)', $apiResource, $referenceType);
102102
}, static function (array $arguments, $apiResource, int $referenceType = UrlGeneratorInterface::ABS_URL) use ($iriConverter): string {
103-
return $iriConverter->getIriFromItem($apiResource, null, $referenceType);
103+
return $iriConverter->getIriFromResource($apiResource, $referenceType);
104104
})
105105
);
106106
}
@@ -245,8 +245,8 @@ private function storeObjectToPublish($object, string $property): void
245245

246246
if ('deletedObjects' === $property) {
247247
$this->deletedObjects[(object) [
248-
'id' => $this->iriConverter->getIriFromItem($object),
249-
'iri' => $this->iriConverter->getIriFromItem($object, null, UrlGeneratorInterface::ABS_URL),
248+
'id' => $this->iriConverter->getIriFromResource($object),
249+
'iri' => $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_URL),
250250
]] = $options;
251251

252252
return;
@@ -271,7 +271,7 @@ private function publishUpdate($object, array $options, string $type): void
271271
$resourceClass = $this->getObjectClass($object);
272272
$context = $options['normalization_context'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation()->getNormalizationContext() ?? [];
273273

274-
$iri = $options['topics'] ?? $this->iriConverter->getIriFromItem($object, null, UrlGeneratorInterface::ABS_URL);
274+
$iri = $options['topics'] ?? $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_URL);
275275
$data = $options['data'] ?? $this->serializer->serialize($object, key($this->formats), $context);
276276
}
277277

0 commit comments

Comments
 (0)