Skip to content

Commit

Permalink
Merge pull request #2980 from bolt/improve-caching
Browse files Browse the repository at this point in the history
Improve caching
  • Loading branch information
bobdenotter authored Nov 29, 2021
2 parents 98f8396 + 2d3eb8f commit fbd72ad
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 87 deletions.
10 changes: 7 additions & 3 deletions config/bolt/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down
9 changes: 9 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ~
Expand Down
4 changes: 3 additions & 1 deletion src/Cache/CachingInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []);
}
45 changes: 37 additions & 8 deletions src/Cache/CachingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -11,18 +12,25 @@ trait CachingTrait
/** @var TagAwareCacheInterface */
private $cache;

/** @var Stopwatch */
private $stopwatch;

/** @var string */
private $cacheKey;
private $cacheKey = '';

/** @var array */
private $cacheTags = [];

/** @var Config */
private $config;

/**
* @required
*/
public function setCache(TagAwareCacheInterface $cache): void
public function setCache(TagAwareCacheInterface $cache, Stopwatch $stopwatch): void
{
$this->cache = $cache;
$this->stopwatch = $stopwatch;
}

/**
Expand All @@ -33,36 +41,57 @@ 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
{
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);
}
}
19 changes: 19 additions & 0 deletions src/Cache/CanonicalCacher.php
Original file line number Diff line number Diff line change
@@ -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]);
}
}
21 changes: 21 additions & 0 deletions src/Cache/ContentToArrayCacher.php
Original file line number Diff line number Diff line change
@@ -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]);
}
}
6 changes: 3 additions & 3 deletions src/Cache/RelatedOptionsUtilityCacher.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down
21 changes: 21 additions & 0 deletions src/Cache/SelectOptionsCacher.php
Original file line number Diff line number Diff line change
@@ -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]);
}
}
3 changes: 3 additions & 0 deletions src/Canonical.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion src/Menu/BackendMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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();
Expand Down
8 changes: 7 additions & 1 deletion src/Menu/FrontendMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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);
Expand Down
55 changes: 17 additions & 38 deletions src/Twig/FieldExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit fbd72ad

Please sign in to comment.