-
-
Notifications
You must be signed in to change notification settings - Fork 971
Improve the performance of CachedIdentifiersExtractor #1257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ final class CachedIdentifiersExtractor implements IdentifiersExtractorInterface | |
| private $cacheItemPool; | ||
| private $propertyAccessor; | ||
| private $decorated; | ||
| private $memoryCache = []; | ||
|
|
||
| public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null) | ||
| { | ||
|
|
@@ -46,60 +47,66 @@ public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersEx | |
| */ | ||
| public function getIdentifiersFromItem($item): array | ||
| { | ||
| $identifiers = []; | ||
| $resourceClass = $this->getObjectClass($item); | ||
|
|
||
| $cacheKey = self::CACHE_KEY_PREFIX.md5($resourceClass); | ||
|
|
||
| // This is to avoid setting the cache twice in the case where the related item cache doesn't exist | ||
| $cacheIsHit = false; | ||
| $keys = $this->getKeys($item, function ($item) use (&$identifiers) { | ||
| return $identifiers = $this->decorated->getIdentifiersFromItem($item); | ||
| }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is cool stuff!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤷♂️
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a reference to an unknown variable and declaring it in a closure looks cool to me haha 😅 |
||
|
|
||
| try { | ||
| $cacheItem = $this->cacheItemPool->getItem($cacheKey); | ||
| $isRelationCached = true; | ||
|
|
||
| if ($cacheIsHit = $cacheItem->isHit()) { | ||
| foreach ($cacheItem->get() as $propertyName) { | ||
| $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); | ||
| if (null !== $identifiers) { | ||
| return $identifiers; | ||
| } | ||
|
|
||
| if (!is_object($identifiers[$propertyName])) { | ||
| continue; | ||
| } | ||
| $identifiers = []; | ||
| foreach ($keys as $propertyName) { | ||
| $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); | ||
|
|
||
| $relatedItem = $identifiers[$propertyName]; | ||
| $relatedCacheKey = self::CACHE_KEY_PREFIX.md5($this->getObjectClass($relatedItem)); | ||
| if (!is_object($identifiers[$propertyName])) { | ||
| continue; | ||
| } | ||
|
|
||
| $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]); | ||
| if (!$relatedIdentifiers = $this->memoryCache[$relatedResourceClass] ?? false) { | ||
| $relatedCacheKey = self::CACHE_KEY_PREFIX.md5($relatedResourceClass); | ||
| try { | ||
| $relatedCacheItem = $this->cacheItemPool->getItem($relatedCacheKey); | ||
|
|
||
| if (!$relatedCacheItem->isHit()) { | ||
| $isRelationCached = false; | ||
| break; | ||
| return $this->decorated->getIdentifiersFromItem($item); | ||
| } | ||
| } catch (CacheException $e) { | ||
| return $this->decorated->getIdentifiersFromItem($item); | ||
| } | ||
|
|
||
| $relatedIdentifiers = $relatedCacheItem->get(); | ||
| } | ||
|
|
||
| unset($identifiers[$propertyName]); | ||
| $identifiers[$propertyName] = $this->propertyAccessor->getValue($identifiers[$propertyName], $relatedIdentifiers[0]); | ||
| } | ||
|
|
||
| $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedCacheItem->get()[0]); | ||
| } | ||
| return $identifiers; | ||
| } | ||
|
|
||
| if (true === $isRelationCached) { | ||
| return $identifiers; | ||
| } | ||
| private function getKeys($item, callable $retriever): array | ||
| { | ||
| $resourceClass = $this->getObjectClass($item); | ||
| if (isset($this->memoryCache[$resourceClass])) { | ||
| return $this->memoryCache[$resourceClass]; | ||
| } | ||
|
|
||
| try { | ||
| $cacheItem = $this->cacheItemPool->getItem(self::CACHE_KEY_PREFIX.md5($resourceClass)); | ||
| if ($cacheItem->isHit()) { | ||
| return $this->memoryCache[$resourceClass] = $cacheItem->get(); | ||
| } | ||
| } catch (CacheException $e) { | ||
| // do nothing | ||
| } | ||
|
|
||
| $identifiers = $this->decorated->getIdentifiersFromItem($item); | ||
| $keys = array_keys($retriever($item)); | ||
|
|
||
| if (isset($cacheItem) && false === $cacheIsHit) { | ||
| try { | ||
| $cacheItem->set(array_keys($identifiers)); | ||
| $this->cacheItemPool->save($cacheItem); | ||
| } catch (CacheException $e) { | ||
| // do nothing | ||
| } | ||
| if (isset($cacheItem)) { | ||
| $cacheItem->set($keys); | ||
| $this->cacheItemPool->save($cacheItem); | ||
| } | ||
|
|
||
| return $identifiers; | ||
| return $this->memoryCache[$resourceClass] = $keys; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,7 +45,9 @@ public function testFirstPass() | |
|
|
||
| $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); | ||
|
|
||
| $this->assertEquals(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); | ||
| $expectedResult = ['id' => 1]; | ||
| $this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy)); | ||
| $this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy), 'Trigger the local cache'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does this test verify that we are triggering the local cache? Also, it should not matter in a unit test.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. He's hacking coverage to please @Simperfit 😆
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, this second call is crucial to test correct behaviour, i.e. that we return the correct value when cache is used. But it's not testing whether we're triggering the local cache.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh wait, we already have different test cases for that... Lol
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really for the coverage. You're right @teohhanhui, it doesn't verify that the local cache actually works (it's very hard to test reliably), however it tests if the local cache doesn't introduce a bug. For instance, if some one contribute this patch: -return $this->memoryCache[$resourceClass];
+return $this->memoryCache[$resourceC];The test will detect the problem. Without this line, it will not.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dunglas Doesn't
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| public function testSecondPass() | ||
|
|
@@ -67,7 +69,9 @@ public function testSecondPass() | |
|
|
||
| $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); | ||
|
|
||
| $this->assertEquals(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); | ||
| $expectedResult = ['id' => 1]; | ||
| $this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy)); | ||
| $this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy), 'Trigger the local cache'); | ||
| } | ||
|
|
||
| public function testSecondPassWithRelatedNotCached() | ||
|
|
@@ -98,7 +102,9 @@ public function testSecondPassWithRelatedNotCached() | |
|
|
||
| $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); | ||
|
|
||
| $expectedResult = ['id' => 1, 'relatedDummy' => 1]; | ||
| $this->assertEquals(['id' => 1, 'relatedDummy' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); | ||
| $this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy), 'Trigger the local cache'); | ||
| } | ||
|
|
||
| public function testSecondPassWithRelatedCached() | ||
|
|
@@ -130,6 +136,8 @@ public function testSecondPassWithRelatedCached() | |
|
|
||
| $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); | ||
|
|
||
| $this->assertEquals(['id' => 1, 'relatedDummy' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); | ||
| $expectedResult = ['id' => 1, 'relatedDummy' => 1]; | ||
| $this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy)); | ||
| $this->assertEquals($expectedResult, $identifiersExtractor->getIdentifiersFromItem($dummy), 'Trigger the local cache'); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming... See #1256 (comment)