Merge pull request #2980 from bolt/improve-caching

Improve caching
This commit is contained in:
Bob den Otter
2021-11-29 09:52:46 +01:00
committed by GitHub
15 changed files with 167 additions and 87 deletions

View File

@@ -9,9 +9,13 @@ secret: '%env(APP_SECRET)%'
# Set the caching configuration for various areas of Bolt.
# The expires_after is counted in seconds.
caching:
related_options: true
expires_after: 3600
caching:
related_options: 1800
canonical: 10
selectoptions: 1800
content_array: 1800
frontend_menu: 3600
backend_menu: 1800
# The theme to use.
#

View File

@@ -93,6 +93,15 @@ services:
Bolt\Cache\RelatedOptionsUtilityCacher:
decorates: Bolt\Utils\RelatedOptionsUtility
Bolt\Cache\CanonicalCacher:
decorates: Bolt\Canonical
Bolt\Cache\SelectOptionsCacher:
decorates: Bolt\Twig\FieldExtension
Bolt\Cache\ContentToArrayCacher:
decorates: Bolt\Twig\JsonExtension
Bolt\Menu\BackendMenuBuilderInterface: '@Bolt\Menu\BackendMenu'
Bolt\Menu\FrontendMenuBuilder: ~

View File

@@ -5,6 +5,8 @@ namespace Bolt\Cache;
interface CachingInterface
{
public function getCacheKey(): string;
public function setCacheKey(string $key): void;
public function setCacheKey(array $tokens): void;
public function execute(callable $fn, array $params = []);
}

View File

@@ -3,6 +3,7 @@
namespace Bolt\Cache;
use Bolt\Configuration\Config;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
@@ -11,8 +12,14 @@ trait CachingTrait
/** @var TagAwareCacheInterface */
private $cache;
/** @var Stopwatch */
private $stopwatch;
/** @var string */
private $cacheKey;
private $cacheKey = '';
/** @var array */
private $cacheTags = [];
/** @var Config */
private $config;
@@ -20,9 +27,10 @@ trait CachingTrait
/**
* @required
*/
public function setCache(TagAwareCacheInterface $cache): void
public function setCache(TagAwareCacheInterface $cache, Stopwatch $stopwatch): void
{
$this->cache = $cache;
$this->stopwatch = $stopwatch;
}
/**
@@ -33,9 +41,9 @@ trait CachingTrait
$this->config = $config;
}
public function setCacheKey(string $key): void
public function setCacheKey(array $tokens): void
{
$this->cacheKey = $key;
$this->cacheKey = self::CACHE_CONFIG_KEY . '_' . md5(implode('', $tokens));
}
public function getCacheKey(): string
@@ -43,26 +51,47 @@ trait CachingTrait
return $this->cacheKey ?? '';
}
public function setCacheTags(array $tags): void
{
$this->cacheTags = $tags;
}
public function getCacheTags(): array
{
return $this->cacheTags;
}
public function execute(callable $fn, array $params = [])
{
$key = $this->getCacheKey();
$this->stopwatch->start('bolt.cache.' . $key);
if ($this->isCachingEnabled()) {
return $this->cache->get($this->getCacheKey(), function(ItemInterface $item) use ($fn, $params) {
$results = $this->cache->get($key, function (ItemInterface $item) use ($fn, $params) {
$item->expiresAfter($this->getExpiresAfter());
$item->tag($this->getCacheTags());
return call_user_func_array($fn, $params);
});
} else {
$results = call_user_func_array($fn, $params);
}
return call_user_func_array($fn, $params);
$this->stopwatch->stop('bolt.cache.' . $key);
return $results;
}
private function isCachingEnabled(): bool
{
return $this->config->get('general/caching/' . self::CACHE_CONFIG_KEY, true);
$configKey = $this->config->get('general/caching/' . self::CACHE_CONFIG_KEY);
return $configKey > 0;
}
private function getExpiresAfter(): int
{
return $this->config->get('general/caching/expires_after', 3600);
return (int) $this->config->get('general/caching/' . self::CACHE_CONFIG_KEY, 3600);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Bolt\Cache;
use Bolt\Canonical;
class CanonicalCacher extends Canonical implements CachingInterface
{
use CachingTrait;
public const CACHE_CONFIG_KEY = 'canonical';
public function generateLink(?string $route, ?array $params, $canonical = false): ?string
{
$this->setCacheKey([$route, $canonical] + $params);
return $this->execute([parent::class, __FUNCTION__], [$route, $params, $canonical]);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Bolt\Cache;
use Bolt\Entity\Content;
use Bolt\Twig\JsonExtension;
class ContentToArrayCacher extends JsonExtension implements CachingInterface
{
use CachingTrait;
public const CACHE_CONFIG_KEY = 'content_array';
protected function contentToArray(Content $content, string $locale = ''): array
{
$this->setCacheKey([$content->getCacheKey($locale)]);
$this->setCacheTags([$content->getCacheKey()]);
return $this->execute([parent::class, __FUNCTION__], [$content, $locale]);
}
}

View File

@@ -6,13 +6,13 @@ use Bolt\Utils\RelatedOptionsUtility;
class RelatedOptionsUtilityCacher extends RelatedOptionsUtility implements CachingInterface
{
public const CACHE_CONFIG_KEY = 'related_options';
use CachingTrait;
public const CACHE_CONFIG_KEY = 'related_options';
public function fetchRelatedOptions(string $contentTypeSlug, string $order, string $format, bool $required, int $maxAmount): array
{
$this->setCacheKey('relatedOptions_' . md5($contentTypeSlug . $order . $format . (string) $required . $maxAmount));
$this->setCacheKey([$contentTypeSlug, $order, $format, (string) $required, $maxAmount]);
return $this->execute([parent::class, __FUNCTION__], [$contentTypeSlug, $order, $format, $required, $maxAmount]);
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Bolt\Cache;
use Bolt\Entity\Field;
use Bolt\Twig\FieldExtension;
class SelectOptionsCacher extends FieldExtension implements CachingInterface
{
use CachingTrait;
public const CACHE_CONFIG_KEY = 'selectoptions';
public function selectOptionsHelper(string $contentTypeSlug, array $params, Field $field, string $format): array
{
$this->setCacheKey([$contentTypeSlug, $format] + $params);
$this->setCacheTags([$contentTypeSlug]);
return $this->execute([parent::class, __FUNCTION__], [$contentTypeSlug, $params, $field, $format]);
}
}

View File

@@ -173,6 +173,9 @@ class Canonical
$this->path = $this->generateLink($route, $params, false);
}
/**
* Decorated by `\Bolt\Utils\CanonicalCacher`
*/
public function generateLink(?string $route, ?array $params, $canonical = false): ?string
{
$removeDefaultLocaleOnCanonical = $this->config->get('general/localization/remove_default_locale_on_canonical', true);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Bolt\Menu;
use Bolt\Configuration\Config;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\Cache\CacheInterface;
@@ -24,12 +25,16 @@ final class BackendMenu implements BackendMenuBuilderInterface
/** @var Stopwatch */
private $stopwatch;
public function __construct(BackendMenuBuilder $menuBuilder, TagAwareCacheInterface $cache, RequestStack $requestStack, Stopwatch $stopwatch)
/** @var Config */
private $config;
public function __construct(BackendMenuBuilder $menuBuilder, TagAwareCacheInterface $cache, RequestStack $requestStack, Stopwatch $stopwatch, Config $config)
{
$this->cache = $cache;
$this->menuBuilder = $menuBuilder;
$this->requestStack = $requestStack;
$this->stopwatch = $stopwatch;
$this->config = $config;
}
public function buildAdminMenu(): array
@@ -40,6 +45,7 @@ final class BackendMenu implements BackendMenuBuilderInterface
$cacheKey = 'bolt.backendMenu_' . $locale;
$menu = $this->cache->get($cacheKey, function (ItemInterface $item) {
$item->expiresAfter($this->config->get('general/caching/backend_menu'));
$item->tag('backendmenu');
return $this->menuBuilder->buildAdminMenu();

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Bolt\Menu;
use Bolt\Configuration\Config;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch;
@@ -26,12 +27,16 @@ final class FrontendMenu implements FrontendMenuBuilderInterface
/** @var Stopwatch */
private $stopwatch;
public function __construct(FrontendMenuBuilder $menuBuilder, TagAwareCacheInterface $cache, RequestStack $requestStack, Stopwatch $stopwatch)
/** @var Config */
private $config;
public function __construct(FrontendMenuBuilder $menuBuilder, TagAwareCacheInterface $cache, RequestStack $requestStack, Stopwatch $stopwatch, Config $config)
{
$this->cache = $cache;
$this->menuBuilder = $menuBuilder;
$this->request = $requestStack->getCurrentRequest();
$this->stopwatch = $stopwatch;
$this->config = $config;
}
public function buildMenu(Environment $twig, ?string $name = null): array
@@ -41,6 +46,7 @@ final class FrontendMenu implements FrontendMenuBuilderInterface
$key = 'bolt.frontendMenu_' . ($name ?: 'main') . '_' . $this->request->getLocale();
$menu = $this->cache->get($key, function (ItemInterface $item) use ($name, $twig) {
$item->expiresAfter($this->config->get('general/caching/frontend_menu'));
$item->tag('frontendmenu');
return $this->menuBuilder->buildMenu($twig, $name);

View File

@@ -16,9 +16,6 @@ use Bolt\Storage\Query;
use Bolt\Utils\ContentHelper;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Tightenco\Collect\Support\Collection;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
@@ -42,28 +39,19 @@ class FieldExtension extends AbstractExtension
/** @var Query */
private $query;
/** @var Stopwatch */
private $stopwatch;
/** @var TagAwareCacheInterface */
private $cache;
public function __construct(
Notifications $notifications,
ContentRepository $contentRepository,
Config $config,
ContentHelper $contentHelper,
Query $query,
Stopwatch $stopwatch,
TagAwareCacheInterface $cache)
Query $query)
{
$this->notifications = $notifications;
$this->contentRepository = $contentRepository;
$this->config = $config;
$this->contentHelper = $contentHelper;
$this->query = $query;
$this->stopwatch = $stopwatch;
$this->cache = $cache;
}
/**
@@ -288,39 +276,30 @@ class FieldExtension extends AbstractExtension
'order' => $order,
];
$options = $this->selectOptionsContentTypeCache($contentTypeSlug, $params, $field, $format);
$options = $this->selectOptionsHelper($contentTypeSlug, $params, $field, $format);
return new Collection($options);
}
private function selectOptionsContentTypeCache(string $contentTypeSlug, array $params, Field $field, string $format)
/**
* Decorated by `\Bolt\Cache\SelectOptionsCacher`
*/
public function selectOptionsHelper(string $contentTypeSlug, array $params, Field $field, string $format): array
{
$cacheKey = 'selectOptions_' . md5($contentTypeSlug . implode('-', $params));
/** @var Content[] $records */
$records = iterator_to_array($this->query->getContent($contentTypeSlug, $params)->getCurrentPageResults());
$this->stopwatch->start('selectOptions');
$options = [];
$options = $this->cache->get($cacheKey, function (ItemInterface $item) use ($contentTypeSlug, $params, $field, $format) {
$item->tag($contentTypeSlug);
/** @var Content[] $records */
$records = iterator_to_array($this->query->getContent($contentTypeSlug, $params)->getCurrentPageResults());
$options = [];
foreach ($records as $record) {
if ($field->getDefinition()->get('mode') === 'format') {
$formattedKey = $this->contentHelper->get($record, $field->getDefinition()->get('format'));
}
$options[] = [
'key' => $formattedKey ?? $record->getId(),
'value' => $this->contentHelper->get($record, $format),
];
foreach ($records as $record) {
if ($field->getDefinition()->get('mode') === 'format') {
$formattedKey = $this->contentHelper->get($record, $field->getDefinition()->get('format'));
}
return $options;
});
$this->stopwatch->stop('selectOptions');
$options[] = [
'key' => $formattedKey ?? $record->getId(),
'value' => $this->contentHelper->get($record, $format),
];
}
return $options;
}

View File

@@ -9,8 +9,6 @@ use Bolt\Entity\Content;
use Bolt\Entity\Field;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigTest;
@@ -30,14 +28,10 @@ class JsonExtension extends AbstractExtension
/** @var Stopwatch */
private $stopwatch;
/** @var TagAwareCacheInterface */
private $cache;
public function __construct(NormalizerInterface $normalizer, Stopwatch $stopwatch, TagAwareCacheInterface $cache)
public function __construct(NormalizerInterface $normalizer, Stopwatch $stopwatch)
{
$this->normalizer = $normalizer;
$this->stopwatch = $stopwatch;
$this->cache = $cache;
}
/**
@@ -95,24 +89,10 @@ class JsonExtension extends AbstractExtension
}, $normalizedRecords);
}
private function contentToArray(Content $content, string $locale = ''): array
{
$cacheKey = 'bolt.contentToArray_' . $content->getCacheKey($locale);
$this->stopwatch->start($cacheKey);
$result = $this->cache->get($cacheKey, function (ItemInterface $item) use ($content, $locale) {
$item->tag($content->getCacheKey());
return $this->contentToArrayCacheHelper($content, $locale);
});
$this->stopwatch->stop($cacheKey);
return $result;
}
private function contentToArrayCacheHelper(Content $content, string $locale = ''): array
/**
* Decorated by `Bolt\Utils\ContentToArrayCacher`
*/
protected function contentToArray(Content $content, string $locale = ''): array
{
$group = [self::SERIALIZE_GROUP];

View File

@@ -11,8 +11,6 @@ use Bolt\Repository\RelationRepository;
use Bolt\Storage\Query;
use Bolt\Utils\ContentHelper;
use Bolt\Utils\RelatedOptionsUtility;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Tightenco\Collect\Support\Collection;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
@@ -35,9 +33,6 @@ class RelatedExtension extends AbstractExtension
/** @var Notifications */
private $notifications;
/** @var TagAwareCacheInterface */
private $cache;
/** @var RelatedOptionsUtility */
private $optionsUtility;
@@ -47,7 +42,6 @@ class RelatedExtension extends AbstractExtension
Query $query,
ContentHelper $contentHelper,
Notifications $notifications,
TagAwareCacheInterface $cache,
RelatedOptionsUtility $optionsUtility)
{
$this->relationRepository = $relationRepository;
@@ -55,7 +49,6 @@ class RelatedExtension extends AbstractExtension
$this->query = $query;
$this->contentHelper = $contentHelper;
$this->notifications = $notifications;
$this->cache = $cache;
$this->optionsUtility = $optionsUtility;
}

View File

@@ -5,6 +5,11 @@ namespace Bolt\Utils;
use Bolt\Entity\Content;
use Bolt\Storage\Query;
/**
* Utility class to get the 'Related Records' as options to show as a pull-down in the Editor.
*
* Decorated by `Bolt\Cache\RelatedOptionsUtilityCacher`
*/
class RelatedOptionsUtility
{
/** @var Query */
@@ -19,6 +24,9 @@ class RelatedOptionsUtility
$this->contentHelper = $contentHelper;
}
/**
* Decorated by `Bolt\Cache\RelatedOptionsUtilityCacher`
*/
public function fetchRelatedOptions(string $contentTypeSlug, string $order, string $format, bool $required, int $maxAmount): array
{
$pager = $this->query->getContent($contentTypeSlug, ['order' => $order])