Skip to content
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

Improve caching #2980

Merged
merged 9 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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