Commit fa1ec9fc authored by s_leu's avatar s_leu Committed by Andrei Mateescu
Browse files

Issue #3254669: Workspace specific caching for certain cache bins

parent 01b00f0d
Loading
Loading
Loading
Loading
+183 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\wse\Cache;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\wse\WorkspaceIdDetector;

/**
 * Defines a workspace aware cache backend.
 *
 * @ingroup cache
 */
class WseCacheBackend implements CacheBackendInterface {

  /**
   * The cache backend returned by the decorated cache backend factory.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $innerBackend;

  /**
   * The workspace ID detector.
   *
   * @var \Drupal\wse\WorkspaceIdDetector
   */
  protected $workspaceIdDetector;

  /**
   * Constructs a WseDatabaseBackend object.
   *
   * @param \Drupal\Core\Cache\CacheBackendInterface $inner_backend
   *   The cache backend returned by the decorated cache backend factory.
   * @param \Drupal\wse\WorkspaceIdDetector $workspace_id_detector
   *   The workspace id detector.
   */
  public function __construct(CacheBackendInterface $inner_backend, WorkspaceIdDetector $workspace_id_detector) {
    $this->innerBackend = $inner_backend;
    $this->workspaceIdDetector = $workspace_id_detector;
  }

  /**
   * {@inheritdoc}
   */
  public function get($cid, $allow_invalid = FALSE) {
    $workspace_aware_cid = $this->getWorkspaceAwareCid($cid);
    return $this->innerBackend->get($workspace_aware_cid, $allow_invalid);
  }

  /**
   * {@inheritdoc}
   */
  public function getMultiple(&$cids, $allow_invalid = FALSE) {
    $workspace_aware_cids = $this->getWorkspaceAwareCids($cids);
    return $this->innerBackend->getMultiple($workspace_aware_cids, $allow_invalid);
  }

  /**
   * {@inheritdoc}
   */
  public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
    $workspace_aware_cid = $this->getWorkspaceAwareCid($cid);
    $this->innerBackend->set($workspace_aware_cid, $data, $expire, $tags);
  }

  /**
   * {@inheritdoc}
   */
  public function setMultiple(array $items) {
    $workspace_id = $this->workspaceIdDetector->getActiveWorkspaceId();
    if ($workspace_id) {
      $workspace_cache_items = [];
      foreach ($items as $cid => $item) {
        $workspace_cache_items[$this->getWorkspaceAwareCid($cid, $workspace_id)] = $item;
      }
      $this->innerBackend->setMultiple($workspace_cache_items);
      return;
    }
    $this->innerBackend->setMultiple($items);
  }

  /**
   * {@inheritdoc}
   */
  public function delete($cid) {
    $this->innerBackend->delete($this->getWorkspaceAwareCid($cid));
  }

  /**
   * {@inheritdoc}
   */
  public function deleteMultiple(array $cids) {
    $workspace_aware_cids = $this->getWorkspaceAwareCids($cids);
    $this->innerBackend->deleteMultiple($workspace_aware_cids);
  }

  /**
   * {@inheritdoc}
   */
  public function deleteAll() {
    $this->innerBackend->deleteAll();
  }

  /**
   * {@inheritdoc}
   */
  public function invalidate($cid) {
    $this->innerBackend->invalidate($this->getWorkspaceAwareCid($cid));
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateMultiple(array $cids) {
    $this->innerBackend->invalidateMultiple($this->getWorkspaceAwareCids($cids));
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateAll() {
    $this->innerBackend->invalidateAll();
  }

  /**
   * {@inheritdoc}
   */
  public function garbageCollection() {
    $this->innerBackend->garbageCollection();
  }

  /**
   * {@inheritdoc}
   */
  public function removeBin() {
    $this->innerBackend->removeBin();
  }

  /**
   * Prefixes given cache ids with the currently active workspace if applicable.
   *
   * @param array $original_cids
   *   The original cache ids before prefixing.
   *
   * @return array
   *   Workspace ID prefixed cids if a workspace is active or the originals.
   */
  protected function getWorkspaceAwareCids(array $original_cids) {
    $workspace_id = $this->workspaceIdDetector->getActiveWorkspaceId();
    if ($workspace_id) {
      $workspace_aware_cids = [];
      foreach ($original_cids as $cid) {
        $workspace_aware_cids[] = $this->getWorkspaceAwareCid($cid, $workspace_id);
      }
      return $workspace_aware_cids;
    }
    return $original_cids;
  }

  /**
   * @param string $workspace_id
   * @param $cid
   * @param array $workspace_aware_cids
   *
   * @return string
   */
  protected function getWorkspaceAwareCid($cid, $workspace_id = NULL) {
    if (!$workspace_id) {
      $workspace_id = $this->workspaceIdDetector->getActiveWorkspaceId();
    }

    if ($workspace_id && strpos($cid, $workspace_id) !== 0) {
      $workspace_aware_cid = $workspace_id . ':' . $cid;
      // The cid may already be workspace specific at this point.
      return $cid != $workspace_aware_cid
        ? $workspace_id . ':' . $cid
        : $cid;
    }
    return $cid;
  }

}
+57 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\wse\Cache;

use Drupal\Core\Cache\CacheFactoryInterface;
use Drupal\wse\WorkspaceIdDetector;

/**
 * Implements the factory for workspace specific cache backends.
 */
class WseCacheBackendFactory implements CacheFactoryInterface {

  /**
   * The decorated cache backend factory.
   *
   * @var \Drupal\wse\WorkspaceIdDetector
   */
  protected $innerFactory;

  /**
   * The workspace ID detector.
   *
   * @var \Drupal\wse\WorkspaceIdDetector
   */
  protected $workspaceIdDetector;

  /**
   * Constructs the DatabaseBackendFactory object.
   *
   * @param \Drupal\Core\Cache\CacheFactoryInterface $inner_factory
   *   The decorated cache backend factory.
   * @param \Drupal\wse\WorkspaceIdDetector $workspace_id_detector
   *   The workspace id detector.
   *
   * @throws \BadMethodCallException
   */
  public function __construct(CacheFactoryInterface $inner_factory, WorkspaceIdDetector $workspace_id_detector) {
    $this->innerFactory = $inner_factory;
    $this->workspaceIdDetector = $workspace_id_detector;
  }

  /**
   * {@inheritdoc}
   */
  public function get($bin) {
    $allowed_bins = ['data', 'discovery'];
    if (!in_array($bin, $allowed_bins, TRUE)) {
      return $this->innerFactory->get($bin);
    }

    return new WseCacheBackend(
      $this->innerFactory->get($bin),
      $this->workspaceIdDetector,
    );
  }

}
+36 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\wse;

use Drupal\wse\Cache\WseCacheBackendFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;

/**
 * Decorates cache factories to make cache entries workspace specific.
 */
class DecorateCacheFactoriesPass implements CompilerPassInterface {

  /**
   * Implements CompilerPassInterface::process().
   */
  public function process(ContainerBuilder $container) {
    $services = $container->getDefinitions();
    foreach ($services as $service_id => $definition) {
      $interfaces = class_implements($definition->getClass());
      if (in_array('Drupal\Core\Cache\CacheFactoryInterface', $interfaces) && $definition->getClass() != 'Drupal\Core\Cache\CacheFactory') {
        $decorated_service_id = $service_id . '.wse';
        if (!in_array($decorated_service_id, $services)) {
          $container->register($decorated_service_id, WseCacheBackendFactory::class)
            ->setDecoratedService($service_id)
            ->setArguments([
              new Reference($decorated_service_id . '.inner'),
              new Reference('wse.workspace_id_detector'),
            ]);
        }
      }
    }
  }

}
+78 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\wse;

use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

/**
 * WorkspaceIdDetector service.
 */
class WorkspaceIdDetector {

  /**
   * The session.
   *
   * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
   */
  protected $session;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * The currently active workspace ID.
   *
   * @var string|null
   */
  static protected $activeWorkspaceId = NULL;

  /**
   * Constructs a WorkspaceIdDetector object.
   *
   * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
   *   The session.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
  public function __construct(SessionInterface $session, RequestStack $request_stack, AccountInterface $current_user) {
    $this->session = $session;
    $this->requestStack = $request_stack;
    $this->currentUser = $current_user;
  }

  /**
   * Retrieves the active workspace ID.
   *
   * @return string|null
   *   The ID of the active workspace, or NULL if there is none.
   */
  public function getActiveWorkspaceId() {
    // @todo Needs to handle all negotiators in a generic way.
    if ($this->currentUser->id() && static::$activeWorkspaceId === NULL) {
      $request = $this->requestStack->getCurrentRequest();
      if (is_string($request->query->get('workspace'))) {
        static::$activeWorkspaceId = $request->query->get('workspace') ?: '';
      }
      else {
        static::$activeWorkspaceId = $this->session->get('active_workspace_id') ?: '';
      }
    }
    return static::$activeWorkspaceId;
  }

}
+20 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\wse;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;

/**
 * Defines a service provider for the Workspace Extras module.
 */
class WseServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function register(ContainerBuilder $container) {
    $container->addCompilerPass(new DecorateCacheFactoriesPass());
  }

}
Loading