diff --git a/config/bolt/config.yaml b/config/bolt/config.yaml index 9b0e98dac..3cdd4952a 100644 --- a/config/bolt/config.yaml +++ b/config/bolt/config.yaml @@ -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. # diff --git a/config/services.yaml b/config/services.yaml index 12eeeab5f..8b1f50117 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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: ~ diff --git a/src/Cache/CachingInterface.php b/src/Cache/CachingInterface.php index 4ad53cbce..c755d655b 100644 --- a/src/Cache/CachingInterface.php +++ b/src/Cache/CachingInterface.php @@ -5,6 +5,8 @@ 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 = []); } diff --git a/src/Cache/CachingTrait.php b/src/Cache/CachingTrait.php index 1e9fa90f4..45bdb7cf2 100644 --- a/src/Cache/CachingTrait.php +++ b/src/Cache/CachingTrait.php @@ -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 @@ public function setConfig(Config $config): void $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 @@ public function getCacheKey(): string 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); } } diff --git a/src/Cache/CanonicalCacher.php b/src/Cache/CanonicalCacher.php new file mode 100644 index 000000000..445ef39a7 --- /dev/null +++ b/src/Cache/CanonicalCacher.php @@ -0,0 +1,19 @@ +setCacheKey([$route, $canonical] + $params); + + return $this->execute([parent::class, __FUNCTION__], [$route, $params, $canonical]); + } +} diff --git a/src/Cache/ContentToArrayCacher.php b/src/Cache/ContentToArrayCacher.php new file mode 100644 index 000000000..763b2c8de --- /dev/null +++ b/src/Cache/ContentToArrayCacher.php @@ -0,0 +1,21 @@ +setCacheKey([$content->getCacheKey($locale)]); + $this->setCacheTags([$content->getCacheKey()]); + + return $this->execute([parent::class, __FUNCTION__], [$content, $locale]); + } +} \ No newline at end of file diff --git a/src/Cache/RelatedOptionsUtilityCacher.php b/src/Cache/RelatedOptionsUtilityCacher.php index 69dfcbe59..e2dc433ca 100644 --- a/src/Cache/RelatedOptionsUtilityCacher.php +++ b/src/Cache/RelatedOptionsUtilityCacher.php @@ -6,13 +6,13 @@ 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]); } diff --git a/src/Cache/SelectOptionsCacher.php b/src/Cache/SelectOptionsCacher.php new file mode 100644 index 000000000..191d8b39a --- /dev/null +++ b/src/Cache/SelectOptionsCacher.php @@ -0,0 +1,21 @@ +setCacheKey([$contentTypeSlug, $format] + $params); + $this->setCacheTags([$contentTypeSlug]); + + return $this->execute([parent::class, __FUNCTION__], [$contentTypeSlug, $params, $field, $format]); + } +} diff --git a/src/Canonical.php b/src/Canonical.php index ad618f2f0..3e8fb4caf 100644 --- a/src/Canonical.php +++ b/src/Canonical.php @@ -173,6 +173,9 @@ public function setPath(?string $route = null, array $params = []): void $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); diff --git a/src/Menu/BackendMenu.php b/src/Menu/BackendMenu.php index 00f35ba8c..ea5b52e65 100644 --- a/src/Menu/BackendMenu.php +++ b/src/Menu/BackendMenu.php @@ -4,6 +4,7 @@ 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 @@ public function buildAdminMenu(): array $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(); diff --git a/src/Menu/FrontendMenu.php b/src/Menu/FrontendMenu.php index 56a877253..c2eecea88 100644 --- a/src/Menu/FrontendMenu.php +++ b/src/Menu/FrontendMenu.php @@ -4,6 +4,7 @@ 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 @@ public function buildMenu(Environment $twig, ?string $name = null): array $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); diff --git a/src/Twig/FieldExtension.php b/src/Twig/FieldExtension.php index 011046be0..80a59a706 100644 --- a/src/Twig/FieldExtension.php +++ b/src/Twig/FieldExtension.php @@ -16,9 +16,6 @@ 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 @@ private function selectOptionsContentType(Field $field): Collection '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)); - - $this->stopwatch->start('selectOptions'); - - $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()); + /** @var Content[] $records */ + $records = iterator_to_array($this->query->getContent($contentTypeSlug, $params)->getCurrentPageResults()); - $options = []; + $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; } diff --git a/src/Twig/JsonExtension.php b/src/Twig/JsonExtension.php index 5f77837ce..50740ad94 100644 --- a/src/Twig/JsonExtension.php +++ b/src/Twig/JsonExtension.php @@ -9,8 +9,6 @@ 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 @@ public function normalizeRecords($records, string $locale = ''): array }, $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]; diff --git a/src/Twig/RelatedExtension.php b/src/Twig/RelatedExtension.php index 1945805ba..f267e2ddd 100644 --- a/src/Twig/RelatedExtension.php +++ b/src/Twig/RelatedExtension.php @@ -11,8 +11,6 @@ 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 @@ public function __construct( Query $query, ContentHelper $contentHelper, Notifications $notifications, - TagAwareCacheInterface $cache, RelatedOptionsUtility $optionsUtility) { $this->relationRepository = $relationRepository; @@ -55,7 +49,6 @@ public function __construct( $this->query = $query; $this->contentHelper = $contentHelper; $this->notifications = $notifications; - $this->cache = $cache; $this->optionsUtility = $optionsUtility; } diff --git a/src/Utils/RelatedOptionsUtility.php b/src/Utils/RelatedOptionsUtility.php index 9ce4286f3..c3c331eb2 100644 --- a/src/Utils/RelatedOptionsUtility.php +++ b/src/Utils/RelatedOptionsUtility.php @@ -5,6 +5,11 @@ 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 @@ public function __construct(Query $query, ContentHelper $contentHelper) $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])