Skip to content

Commit 89276bd

Browse files
author
abluchet
committed
Improve #1781 by using ResourceClassResolver
1 parent 729042f commit 89276bd

File tree

7 files changed

+130
-34
lines changed

7 files changed

+130
-34
lines changed

src/Api/CachedIdentifiersExtractor.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,20 @@ final class CachedIdentifiersExtractor implements IdentifiersExtractorInterface
3333
private $cacheItemPool;
3434
private $propertyAccessor;
3535
private $decorated;
36+
private $resourceClassResolver;
3637
private $localCache = [];
3738
private $localResourceCache = [];
3839

39-
public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null)
40+
public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null)
4041
{
4142
$this->cacheItemPool = $cacheItemPool;
4243
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
4344
$this->decorated = $decorated;
45+
$this->resourceClassResolver = $resourceClassResolver;
46+
47+
if (null === $this->resourceClassResolver) {
48+
@trigger_error(sprintf('Not injecting %s in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), E_USER_DEPRECATED);
49+
}
4450
}
4551

4652
/**
@@ -76,12 +82,12 @@ public function getIdentifiersFromItem($item): array
7682
continue;
7783
}
7884

79-
if (method_exists($identifiers[$propertyName], '__toString')) {
80-
$identifiers[$propertyName] = (string) $identifiers[$propertyName];
85+
$relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
86+
87+
if (null !== $this->resourceClassResolver && !$this->resourceClassResolver->isResourceClass($relatedResourceClass)) {
8188
continue;
8289
}
8390

84-
$relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
8591
if (!$relatedIdentifiers = $this->localCache[$relatedResourceClass] ?? false) {
8692
$relatedCacheKey = self::CACHE_KEY_PREFIX.md5($relatedResourceClass);
8793
try {

src/Api/IdentifiersExtractor.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,18 @@ final class IdentifiersExtractor implements IdentifiersExtractorInterface
3232
private $propertyNameCollectionFactory;
3333
private $propertyMetadataFactory;
3434
private $propertyAccessor;
35+
private $resourceClassResolver;
3536

36-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null)
37+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null)
3738
{
3839
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
3940
$this->propertyMetadataFactory = $propertyMetadataFactory;
4041
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
42+
$this->resourceClassResolver = $resourceClassResolver;
43+
44+
if (null === $this->resourceClassResolver) {
45+
@trigger_error(sprintf('Not injecting %s in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), E_USER_DEPRECATED);
46+
}
4147
}
4248

4349
/**
@@ -68,14 +74,19 @@ public function getIdentifiersFromItem($item): array
6874
if (null === $identifier || false === $identifier) {
6975
continue;
7076
}
77+
7178
$identifier = $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
79+
7280
if (!\is_object($identifier)) {
7381
continue;
74-
} elseif (method_exists($identifier, '__toString')) {
75-
$identifiers[$propertyName] = (string) $identifier;
76-
continue;
7782
}
83+
7884
$relatedResourceClass = $this->getObjectClass($identifier);
85+
86+
if (null !== $this->resourceClassResolver && !$this->resourceClassResolver->isResourceClass($relatedResourceClass)) {
87+
continue;
88+
}
89+
7990
$relatedItem = $identifier;
8091
unset($identifiers[$propertyName]);
8192
foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {

src/Api/ResourceClassResolver.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ final class ResourceClassResolver implements ResourceClassResolverInterface
2828
use ClassInfoTrait;
2929

3030
private $resourceNameCollectionFactory;
31+
private $localIsResourceClassCache = [];
3132

3233
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory)
3334
{
@@ -69,12 +70,16 @@ public function getResourceClass($value, string $resourceClass = null, bool $str
6970
*/
7071
public function isResourceClass(string $type): bool
7172
{
73+
if (isset($this->localIsResourceClassCache[$type])) {
74+
return $this->localIsResourceClassCache[$type];
75+
}
76+
7277
foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
7378
if ($type === $resourceClass) {
74-
return true;
79+
return $this->localIsResourceClassCache[$type] = true;
7580
}
7681
}
7782

78-
return false;
83+
return $this->localIsResourceClassCache[$type] = false;
7984
}
8085
}

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,14 @@
217217
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
218218
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
219219
<argument type="service" id="api_platform.property_accessor" />
220+
<argument type="service" id="api_platform.resource_class_resolver" />
220221
</service>
221222

222223
<service id="api_platform.identifiers_extractor.cached" class="ApiPlatform\Core\Api\CachedIdentifiersExtractor" decorates="api_platform.identifiers_extractor" public="false">
223224
<argument type="service" id="api_platform.cache.identifiers_extractor" />
224225
<argument type="service" id="api_platform.identifiers_extractor.cached.inner" />
225226
<argument type="service" id="api_platform.property_accessor" />
227+
<argument type="service" id="api_platform.resource_class_resolver" />
226228
</service>
227229

228230
<!-- Subresources -->

tests/Api/CachedIdentifiersExtractorTest.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515

1616
use ApiPlatform\Core\Api\CachedIdentifiersExtractor;
1717
use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
18+
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
1819
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Doctrine\Generator\Uuid;
1920
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
2021
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
2122
use PHPUnit\Framework\TestCase;
23+
use Prophecy\Argument;
2224
use Psr\Cache\CacheItemInterface;
2325
use Psr\Cache\CacheItemPoolInterface;
2426

@@ -54,7 +56,7 @@ public function testFirstPass($identifier, $identifierValue)
5456
$decoration = $this->prophesize(IdentifiersExtractorInterface::class);
5557
$decoration->getIdentifiersFromItem($dummy)->shouldBeCalled()->willReturn(['id' => $identifierValue]);
5658

57-
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null);
59+
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null, $this->getResourceClassResolver());
5860

5961
$expectedResult = ['id' => $identifierValue];
6062
$this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy));
@@ -87,7 +89,7 @@ public function testSecondPass($identifier, $identifierValue)
8789
$decoration = $this->prophesize(IdentifiersExtractorInterface::class);
8890
$decoration->getIdentifiersFromItem($dummy)->shouldNotBeCalled();
8991

90-
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null);
92+
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null, $this->getResourceClassResolver());
9193

9294
$expectedResult = ['id' => $identifierValue];
9395
$this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy));
@@ -120,7 +122,7 @@ public function testFirstPassWithRelated()
120122
$decoration = $this->prophesize(IdentifiersExtractorInterface::class);
121123
$decoration->getIdentifiersFromItem($dummy)->shouldBeCalled()->willReturn(['id' => 1, 'relatedDummy' => 1]);
122124

123-
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null);
125+
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null, $this->getResourceClassResolver());
124126

125127
$expectedResult = ['id' => 1, 'relatedDummy' => 1];
126128
$this->assertEquals(['id' => 1, 'relatedDummy' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy));
@@ -154,10 +156,36 @@ public function testSecondPassWithRelated()
154156
$decoration = $this->prophesize(IdentifiersExtractorInterface::class);
155157
$decoration->getIdentifiersFromItem($dummy)->shouldNotBeCalled();
156158

157-
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null);
159+
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null, $this->getResourceClassResolver());
158160

159161
$expectedResult = ['id' => 1, 'relatedDummy' => 1];
160162
$this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy));
161163
$this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy), 'Trigger the local cache');
162164
}
165+
166+
/**
167+
* @group legacy
168+
* @expectedDeprecation Not injecting ApiPlatform\Core\Api\ResourceClassResolverInterface in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.
169+
*/
170+
public function testDeprecationResourceClassResolver()
171+
{
172+
$cacheItemPool = $this->prophesize(CacheItemPoolInterface::class);
173+
$decoration = $this->prophesize(IdentifiersExtractorInterface::class);
174+
175+
$identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null);
176+
}
177+
178+
private function getResourceClassResolver()
179+
{
180+
$resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class);
181+
$resourceClassResolver->isResourceClass(Argument::type('string'))->will(function ($args) {
182+
if (Uuid::class === $args[0]) {
183+
return false;
184+
}
185+
186+
return true;
187+
});
188+
189+
return $resourceClassResolver->reveal();
190+
}
163191
}

tests/Api/IdentifiersExtractorTest.php

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Tests\Api;
1515

1616
use ApiPlatform\Core\Api\IdentifiersExtractor;
17+
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
1718
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
1819
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
1920
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
@@ -22,6 +23,7 @@
2223
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
2324
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
2425
use PHPUnit\Framework\TestCase;
26+
use Prophecy\Argument;
2527

2628
/**
2729
* @author Antoine Bluchet <[email protected]>
@@ -53,7 +55,7 @@ public function testGetIdentifiersFromResourceClass()
5355
{
5456
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id']);
5557

56-
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
58+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $this->getResourceClassResolver());
5759

5860
$this->assertEquals(['id'], $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class));
5961
}
@@ -62,7 +64,7 @@ public function testGetCompositeIdentifiersFromResourceClass()
6264
{
6365
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'name']);
6466

65-
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
67+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $this->getResourceClassResolver());
6668

6769
$this->assertEquals(['id', 'name'], $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class));
6870
}
@@ -71,31 +73,32 @@ public function testGetIdentifiersFromItem()
7173
{
7274
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id']);
7375

74-
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
76+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $this->getResourceClassResolver());
7577

7678
$dummy = new Dummy();
7779
$dummy->setId(1);
7880

7981
$this->assertEquals(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy));
8082
}
8183

82-
public function testGetStringableIdentifiersFromItem()
84+
public function testUseObjectIdentifier()
8385
{
8486
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id']);
8587

86-
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
88+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $this->getResourceClassResolver());
8789

90+
$uuid = new Uuid();
8891
$dummy = new Dummy();
89-
$dummy->setId(new Uuid());
92+
$dummy->setId($uuid);
9093

91-
$this->assertEquals(['id' => 'foo'], $identifiersExtractor->getIdentifiersFromItem($dummy));
94+
$this->assertEquals(['id' => $uuid], $identifiersExtractor->getIdentifiersFromItem($dummy));
9295
}
9396

9497
public function testGetCompositeIdentifiersFromItem()
9598
{
9699
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'name']);
97100

98-
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
101+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $this->getResourceClassResolver());
99102

100103
$dummy = new Dummy();
101104
$dummy->setId(1);
@@ -109,7 +112,7 @@ public function testGetRelatedIdentifiersFromItem()
109112
$prophecies = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'relatedDummy']);
110113
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(RelatedDummy::class, ['id'], $prophecies);
111114

112-
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
115+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $this->getResourceClassResolver());
113116

114117
$related = new RelatedDummy();
115118
$related->setId(2);
@@ -130,7 +133,7 @@ public function testThrowNoIdentifierFromItem()
130133
$prophecies = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'relatedDummy']);
131134
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(RelatedDummy::class, [], $prophecies);
132135

133-
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
136+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $this->getResourceClassResolver());
134137

135138
$related = new RelatedDummy();
136139
$related->setId(2);
@@ -141,4 +144,34 @@ public function testThrowNoIdentifierFromItem()
141144

142145
$identifiersExtractor->getIdentifiersFromItem($dummy);
143146
}
147+
148+
private function getResourceClassResolver()
149+
{
150+
$resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class);
151+
$resourceClassResolver->isResourceClass(Argument::type('string'))->will(function ($args) {
152+
if (Uuid::class === $args[0]) {
153+
return false;
154+
}
155+
156+
return true;
157+
});
158+
159+
return $resourceClassResolver->reveal();
160+
}
161+
162+
/**
163+
* @group legacy
164+
* @expectedDeprecation Not injecting ApiPlatform\Core\Api\ResourceClassResolverInterface in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.
165+
*/
166+
public function testLegacyGetIdentifiersFromItem()
167+
{
168+
list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id']);
169+
170+
$identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal());
171+
172+
$dummy = new Dummy();
173+
$dummy->setId(1);
174+
175+
$this->assertEquals(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy));
176+
}
144177
}

0 commit comments

Comments
 (0)