Skip to content
Open
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
9 changes: 8 additions & 1 deletion drush.services.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
services:
wmpage_cache.commands.cache-clear:
class: Drupal\wmpage_cache\Commands\CacheClearCommands
arguments: ['@wmpage_cache.storage']
tags: [{ name: drush.command }]
arguments:
- '@wmpage_cache.storage'

wmpage_cache.commands.cache-warmup:
class: Drupal\wmpage_cache\Commands\CacheWarmupCommands
tags: [{ name: drush.command }]
arguments:
- '@wmpage_cache.heater'
83 changes: 83 additions & 0 deletions src/CacheHeater.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace Drupal\wmpage_cache;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Url;
use Drupal\user\Entity\User;
use Drupal\wmpage_cache\Plugin\QueueWorker\CacheHeaterQueueWorker;

class CacheHeater implements CacheHeaterInterface
{
/** @var EntityTypeManagerInterface */
protected $entityTypeManager;
/** @var QueueFactory */
protected $queueFactory;
/** @var array */
protected $includedPages;

public function __construct(
EntityTypeManagerInterface $entityTypeManager,
QueueFactory $queueFactory,
array $includedPages
) {
$this->entityTypeManager = $entityTypeManager;
$this->queueFactory = $queueFactory;
$this->includedPages = $includedPages;
}

public function warmup(array $entries): void
{
$queue = $this->queueFactory->get(CacheHeaterQueueWorker::ID);

foreach ($entries as $entry) {
$queue->createItem($entry->getUri());
}
}

public function warmupAll(): void
{
$urls = [];

foreach ($this->includedPages['entities'] ?? [] as $entityTypeId => $bundles) {
$definition = $this->entityTypeManager->getDefinition($entityTypeId);

if (!$definition->hasLinkTemplate('canonical')) {
throw new \InvalidArgumentException(
"Entity type with id ${$entityTypeId} does not have a canonical route"
);
}

$query = $this->entityTypeManager
->getStorage($entityTypeId)
->getQuery();

if (!empty($bundles)) {
$query->condition($definition->getKey('bundle'), $bundles, 'IN');
}

foreach ($query->execute() as $id) {
$urls[] = Url::fromRoute(
"entity.${entityTypeId}.canonical",
[$entityTypeId => $id]
);
}
}

foreach ($this->includedPages['routes'] as $routeName => $routeParameters) {
$urls[] = Url::fromRoute($routeName, $routeParameters);
}

$queue = $this->queueFactory->get(CacheHeaterQueueWorker::ID);
$anonymousUser = User::load(0);

foreach ($urls as $url) {
if (!$url->access($anonymousUser)) {
continue;
}

$queue->createItem($url->setAbsolute(false)->toString());
}
}
}
11 changes: 11 additions & 0 deletions src/CacheHeaterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Drupal\wmpage_cache;

interface CacheHeaterInterface
{
/** @var Cache[] $entries */
public function warmup(array $entries): void;

public function warmupAll(): void;
}
29 changes: 29 additions & 0 deletions src/Commands/CacheWarmupCommands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Drupal\wmpage_cache\Commands;

use Drupal\wmpage_cache\CacheHeaterInterface;
use Drush\Commands\DrushCommands;

class CacheWarmupCommands extends DrushCommands
{
/** @var CacheHeaterInterface */
protected $heater;

public function __construct(
CacheHeaterInterface $heater
) {
$this->heater = $heater;
}

/**
* Warm up the page cache of all configured pages
*
* @command wmpage_cache:warmup
* @aliases wmpage-cache-warmup,wmpcw
*/
public function cacheWarmup(): void
{
$this->heater->warmupAll();
}
}
20 changes: 15 additions & 5 deletions src/Invalidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,28 @@

class Invalidator implements InvalidatorInterface
{
/** @var \Drupal\wmpage_cache\Storage\StorageInterface */
/** @var StorageInterface */
protected $storage;
/** @var CacheHeaterInterface */
protected $heater;

public function __construct(StorageInterface $storage)
{
public function __construct(
StorageInterface $storage,
CacheHeaterInterface $heater
) {
$this->storage = $storage;
$this->heater = $heater;
}

public function invalidateCacheTags(array $tags)
{
$this->storage->remove(
$this->storage->getByTags($tags)
$entries = $this->storage->getByTags($tags);
$ids = array_map(
function (Cache $entry) { return $entry->getId(); },
$entries
);

$this->storage->remove($ids);
$this->heater->warmup($entries);
}
}
5 changes: 5 additions & 0 deletions src/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class Manager implements CacheTagsInvalidatorInterface
protected $cacheKeyGenerator;
/** @var CacheBuilderInterface */
protected $cacheBuilder;
/** @var CacheHeaterInterface */
protected $heater;
/** @var bool */
protected $storeCache;
/** @var bool */
Expand All @@ -39,6 +41,7 @@ public function __construct(
InvalidatorInterface $invalidator,
CacheKeyGeneratorInterface $cacheKeyGenerator,
CacheBuilderInterface $cacheBuilder,
CacheHeaterInterface $heater,
$storeCache,
$storeTags,
$maxPurgesPerInvalidation,
Expand All @@ -50,6 +53,7 @@ public function __construct(
$this->invalidator = $invalidator;
$this->cacheKeyGenerator = $cacheKeyGenerator;
$this->cacheBuilder = $cacheBuilder;
$this->heater = $heater;
$this->storeCache = $storeCache && $storeTags;
$this->storeTags = $storeTags;
$this->maxPurgesPerInvalidation = $maxPurgesPerInvalidation;
Expand Down Expand Up @@ -112,6 +116,7 @@ public function invalidateTags(array $tags)
foreach ($this->flushTriggerTags as $re) {
if (preg_match('#' . $re . '#', $tag)) {
$this->storage->flush();
$this->heater->warmupAll();
return;
}
}
Expand Down
53 changes: 53 additions & 0 deletions src/Plugin/QueueWorker/CacheHeaterQueueWorker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Drupal\wmpage_cache\Plugin\QueueWorker;

use Drupal\Core\Annotation\QueueWorker;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* @QueueWorker(
* id = \Drupal\wmpage_cache\Plugin\QueueWorker\CacheHeaterQueueWorker::ID,
* title = @Translation("Hit a certain path to warm up the page cache."),
* cron = {"time" : 30}
* )
*/
class CacheHeaterQueueWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface
{
public const ID = 'wmpage_cache.heater';

/** @var Client */
protected $client;
/** @var string */
protected $hostName;

public static function create(
ContainerInterface $container,
array $configuration,
$pluginId, $pluginDefinition
) {
$instance = new static($configuration, $pluginId, $pluginDefinition);
$instance->client = $container->get('wmpage_cache.heater.client');
$instance->hostName = $container->getParameter('wmpage_cache.heater.host_name');

return $instance;
}

public function processItem($uri)
{
try {
$this->client->get($uri, [
'headers' => [
// @see https://github.com/guzzle/guzzle/issues/1297
'Host' => $this->hostName,
],
]);
} catch (GuzzleException $e) {
watchdog_exception('wmpage_cache.heater', $e);
}
}
}
19 changes: 15 additions & 4 deletions src/Storage/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public function getExpired($amount)
return $q->execute()->fetchAll(\PDO::FETCH_COLUMN);
}

public function getByTags(array $tags)
public function getByTags(array $tags): array
{
if (!$tags) {
return [];
Expand All @@ -142,13 +142,24 @@ public function getByTags(array $tags)
}

$q = $this->db->select(self::TABLE_ENTRIES, 'c')
->fields('c', ['id']);
->fields('c', []);
$q->condition('c.expiry', time(), '>=');
$q->innerJoin(self::TABLE_TAGS, 't', 't.id = c.id');
$q->condition('t.tag', $tags, 'IN');

$ids = $q->execute()->fetchAll(\PDO::FETCH_COLUMN);
return $ids;
return array_map(
static function (array $data) {
return new Cache(
$data['id'],
$data['uri'],
$data['method'],
$data['content'],
unserialize($data['headers'], ['allowed_classes' => false]),
$data['expiry']
);
},
$q->execute()->fetchAll(\PDO::FETCH_ASSOC)
);
}

public function remove(array $ids)
Expand Down
4 changes: 2 additions & 2 deletions src/Storage/StorageInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public function set(Cache $item, array $tags);
*
* @param string[] $tags
*
* @return string[] The cache ids
* @return Cache[] The cache ids
*/
public function getByTags(array $tags);
public function getByTags(array $tags): array;

/**
* Remove expired items from storage.
Expand Down
27 changes: 27 additions & 0 deletions wmpage_cache.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ parameters:
# use a CDN that charges for invalidations. Set this number much lower.
wmpage_cache.max_purges_per_invalidation: 100000

wmpage_cache.heater.host_name: ''
wmpage_cache.heater.base_url: 'https://localhost'
wmpage_cache.heater.included_pages:
entities:
node: { }
routes:
my_module.custom_route:
route_parameter_key: route_parameter_value
my_module.other_custom_route: { }

services:
wmpage_cache.storage:
class: Drupal\wmpage_cache\Storage\StorageInterface
Expand Down Expand Up @@ -176,6 +186,7 @@ services:
- '@wmpage_cache.invalidator'
- '@wmpage_cache.keygenerator'
- '@wmpage_cache.builder'
- '@wmpage_cache.heater'
- '%wmpage_cache.store%'
- '%wmpage_cache.tags%'
- '%wmpage_cache.max_purges_per_invalidation%'
Expand Down Expand Up @@ -219,6 +230,7 @@ services:
class: Drupal\wmpage_cache\Invalidator
arguments:
- '@wmpage_cache.storage'
- '@wmpage_cache.heater'

wmpage_cache.maxage.default:
class: Drupal\wmpage_cache\MaxAgeDecider
Expand Down Expand Up @@ -255,3 +267,18 @@ services:
class: Drupal\wmpage_cache\GlobalCacheableMetadata
arguments:
- '@renderer'

wmpage_cache.heater:
class: Drupal\wmpage_cache\CacheHeater
arguments:
- '@entity_type.manager'
- '@queue'
- '%wmpage_cache.heater.included_pages%'

wmpage_cache.heater.client:
class: GuzzleHttp\Client
arguments:
- base_uri: '%wmpage_cache.heater.base_url%'
headers:
host: '%wmpage_cache.heater.host_name%'
verify: false