-
Notifications
You must be signed in to change notification settings - Fork 351
Add compiler pass to decorate ResourceLoaders with an in memory cache #7850
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
Add compiler pass to decorate ResourceLoaders with an in memory cache #7850
Conversation
|
||
use Symfony\Contracts\Service\ResetInterface; | ||
|
||
class CachedResourceLoader implements ResourceLoaderInterface, ResetInterface |
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.
Mark as @internal
we maybe change the caching mechanic in future.
public function load(array $ids, ?string $locale, array $params = []): array | ||
{ | ||
$cacheKey = $this->generateCacheKey($ids, $locale, $params); | ||
|
||
if (isset($this->cache[$cacheKey])) { | ||
return $this->cache[$cacheKey]; | ||
} | ||
|
||
$result = $this->decoratedResourceLoader->load($ids, $locale, $params); | ||
$this->cache[$cacheKey] = $result; | ||
|
||
return $result; | ||
} |
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.
This looks unexpected we do NOT use here a memoize based cache mechanic here.
We need cache here by id
(single) and merge it for next call. We can have first call with [1, 2]
second with [1, 3]
then the decorated is only called with [3]
.:
public function load(array $ids, ?string $locale, array $params = []): array | |
{ | |
$cacheKey = $this->generateCacheKey($ids, $locale, $params); | |
if (isset($this->cache[$cacheKey])) { | |
return $this->cache[$cacheKey]; | |
} | |
$result = $this->decoratedResourceLoader->load($ids, $locale, $params); | |
$this->cache[$cacheKey] = $result; | |
return $result; | |
} | |
public function load(array $ids, ?string $locale, array $params = []): array | |
{ | |
$notFoundIds = []; | |
$cachedResult = []; | |
foreach ($ids as $id) { | |
$cacheKey = $id; | |
if (!isset($this->cache[$cacheKey])) { | |
$notFoundIds = []; | |
continue; | |
} | |
$cachedResult[$id] = $this->cache[$cacheKey]; | |
} | |
$result = $this->decoratedResourceLoader->load($ids, $locale, $params); | |
// cache new result | |
// merge with cached result | |
return $result; | |
} |
{ | ||
$this->decoratedResourceLoader = $this->prophesize(ResourceLoaderInterface::class); | ||
$this->cachedResourceLoader = new CachedResourceLoader( | ||
$this->decoratedResourceLoader->reveal() |
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.
Would try to not mock instead create anonymous
class implementation which unset
an array and would throw a exception if it calls the same id again:
public static array $values = [];
public function load(array $ids, ?string $locale, array $params = []): array
{
$models = [];
foreach ($ids as $id) {
if (!isset(self::$values[$id])) {
throw new LogicException('The CachedResourceLoader should never call the same "id" multiple times. Id "%s" requested again');
}
$models = self::$values[$ids];
unset(self::$values[$ids]); // unset to test not called again with same id
}
return $models;
}
You can at start of every test refill it with:
$this->decoratedResourceLoader::$values = [1 => ...]
This way we not require any mocking.
use Symfony\Component\DependencyInjection\Definition; | ||
use Symfony\Component\DependencyInjection\Reference; | ||
|
||
class ResourceLoaderCacheCompilerPassTest extends TestCase |
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.
we should not sue a mocked test here we should create a test using AbstractCompilerPassTestCase
in this case.
52c63d2
to
da77910
Compare
->setPublic(false); | ||
|
||
/** @var array<string, string> $tag */ | ||
foreach ($tags as $tag) { |
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.
@Prokyonn verify if necessary
...s/content/src/Infrastructure/Symfony/HttpKernel/Compiler/ResourceLoaderCacheCompilerPass.php
Outdated
Show resolved
Hide resolved
3387c17
to
eec5635
Compare
…r/ResourceLoaderCacheCompilerPass.php Co-authored-by: Alexander Schranz <alexander@sulu.io>
eec5635
to
a8382bd
Compare
What's in this PR?
Adds a CompilerPass that decorates all ResourceLoaders with an in-memory cache.
Why?
In some edge-cases we have to query the same resource twice, so we need a caching layer that only lives for the current request.