depcalc-8.x-1.x-dev/src/Cache/DepcalcCacheBackend.php
src/Cache/DepcalcCacheBackend.php
<?php namespace Drupal\depcalc\Cache; use Drupal\Component\Uuid\Uuid; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; use Drupal\Core\Cache\DatabaseBackend; use Drupal\depcalc\DependencyCalculatorEvents; use Drupal\depcalc\Event\InvalidateDependenciesEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Class DepcalcCacheBackend. * * Provides a depcalc specific cache backend that can invalidate depcalc cache * tags in the database table. This class also works as an adapter, so if * another cache backend is passed to it, it will proxy calls, including tag * invalidation, to that backend. * * @package Drupal\depcalc\Cache */ class DepcalcCacheBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface { /** * The cache backend to decorate. * * @var \Drupal\Core\Cache\CacheBackendInterface */ protected $backend; /** * The event dispatcher. * * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ protected $dispatcher; /** * The database connection. * * @var \Drupal\Core\Database\Connection */ protected $connection; /** * The bin name. * * @var string */ protected $bin; /** * DepcalcCacheBackend constructor. * * @param \Drupal\Core\Cache\CacheBackendInterface $backend * The CacheBackendInterface object to decorate. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher * The event dispatcher. * * @throws \ReflectionException */ public function __construct(CacheBackendInterface $backend, EventDispatcherInterface $dispatcher) { $this->backend = $backend; $this->dispatcher = $dispatcher; if ($backend instanceof DatabaseBackend) { $this->getProperties($backend); } } /** * Get the DatabaseBackend instance's database connection. * * We don't want to get our own database connection or cache bin, so we just * reflect it out of the object we're decorating. * * @param \Drupal\Core\Cache\DatabaseBackend $backend * The database backend object from which to extract necessary properties. * * @throws \ReflectionException */ private function getProperties(DatabaseBackend $backend) : void { $r = new \ReflectionObject($backend); $p = $r->getProperty('connection'); $p->setAccessible(TRUE); $this->connection = $p->getValue($backend); $p = $r->getProperty('bin'); $p->setAccessible(TRUE); $this->bin = $p->getValue($backend); } /** * {@inheritdoc} */ public function invalidateMultiple(array $cids, $allow_invalid = FALSE) { $original_cids = $cids; // $this->getMultiple($cids) removes the successfully obtained $cids. // And because it is passed by reference then we need to invalidate // the original $cids. $cache_objects = $this->getMultiple($cids, $allow_invalid); $this->backend->invalidateMultiple($original_cids); if (!$cache_objects) { return; } /** @var \Drupal\depcalc\DependentEntityWrapperInterface[] $wrappers */ $wrappers = array_map(function ($cache) { return $cache->data; }, $cache_objects); $event = new InvalidateDependenciesEvent($wrappers); $this->dispatcher->dispatch($event, DependencyCalculatorEvents::INVALIDATE_DEPENDENCIES); } /** * {@inheritdoc} */ public function get($cid, $allow_invalid = FALSE) { return $this->backend->get($cid, $allow_invalid); } /** * {@inheritdoc} */ public function getMultiple(&$cids, $allow_invalid = FALSE) { return $this->backend->getMultiple($cids, $allow_invalid); } /** * {@inheritdoc} */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) { $this->backend->set($cid, $data, $expire, $tags); } /** * {@inheritdoc} */ public function setMultiple(array $items) { $this->backend->setMultiple($items); } /** * {@inheritdoc} */ public function delete($cid) { // Invalidate first to handle the dependencies. $this->invalidate($cid); $this->backend->delete($cid); } /** * {@inheritdoc} */ public function deleteMultiple(array $cids) { $this->backend->deleteMultiple($cids); } /** * {@inheritdoc} */ public function deleteAll() { // This cache doesn't need to be deleted when doing cache rebuild. // We do nothing here. } /** * Deletes all cache items in a bin when explicitly called. */ public function deleteAllPermanent(): void { $this->backend->deleteAll(); } /** * {@inheritdoc} */ public function invalidate($cid) { $this->invalidateMultiple([$cid]); } /** * {@inheritdoc} */ public function invalidateAll() { $this->backend->invalidateAll(); } /** * {@inheritdoc} */ public function garbageCollection() { $this->backend->garbageCollection(); } /** * {@inheritdoc} */ public function removeBin() { $this->backend->removeBin(); } /** * {@inheritdoc} */ public function invalidateTags(array $tags) { if (!Uuid::isValid(reset($tags))) { // If the first item is not a UUID then none of them are. return; } if ($this->backend instanceof DatabaseBackend) { // On module install, this will get called, so let's check that the table // exists before doing anything. if (!$this->connection->schema()->tableExists($this->bin)) { return; } foreach ($tags as $tag) { $result = $this->connection->select($this->bin, 'bin') ->fields('bin', ['cid']) ->condition('tags', "%{$this->connection->escapeLike($tag)}%", 'LIKE') ->execute(); $cids = $result->fetchCol(); if (empty($cids)) { continue; } // Allow for invalidated cache to be invalidated too. $this->invalidateMultiple($cids, TRUE); } } elseif ($this->backend instanceof CacheTagsInvalidatorInterface) { $this->backend->invalidateTags($tags); } } }