CachePluginBase.php 11.6 KB
Newer Older
merlinofchaos's avatar
merlinofchaos committed
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\views\Plugin\views\cache\CachePluginBase.
merlinofchaos's avatar
merlinofchaos committed
6 7
 */

8
namespace Drupal\views\Plugin\views\cache;
9

10
use Drupal\Core\Cache\Cache;
11
use Drupal\Core\Render\RendererInterface;
12
use Drupal\views\Plugin\views\PluginBase;
13
use Drupal\Core\Database\Query\Select;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15

merlinofchaos's avatar
merlinofchaos committed
16 17 18
/**
 * @defgroup views_cache_plugins Views cache plugins
 * @{
19
 * Plugins to handle Views caches.
20
 *
21
 * Cache plugins control how caching is done in Views.
22 23 24 25 26 27 28
 *
 * Cache plugins extend \Drupal\views\Plugin\views\cache\CachePluginBase.
 * They must be annotated with \Drupal\views\Annotation\ViewsCache
 * annotation, and must be in namespace directory Plugin\views\cache.
 *
 * @ingroup views_plugins
 * @see plugin_api
merlinofchaos's avatar
merlinofchaos committed
29 30 31 32 33
 */

/**
 * The base plugin to handle caching.
 */
34
abstract class CachePluginBase extends PluginBase {
35

merlinofchaos's avatar
merlinofchaos committed
36 37 38 39 40 41
  /**
   * Contains all data that should be written/read from cache.
   */
  var $storage = array();

  /**
42 43 44 45 46 47 48 49 50 51
   * Which cache bin to store the rendered output in.
   *
   * @var string
   */
  protected $outputBin = 'render';

  /**
   * Which cache bin to store query results in.
   *
   * @var string
merlinofchaos's avatar
merlinofchaos committed
52
   */
53
  protected $resultsBin = 'data';
merlinofchaos's avatar
merlinofchaos committed
54

55
  /**
56 57 58
   * Stores the cache ID used for the results cache.
   *
   * The cache ID is stored in generateResultsKey() got executed.
59 60
   *
   * @var string
61
   *
62
   * @see \Drupal\views\Plugin\views\cache\CachePluginBase::generateResultsKey()
63
   */
64
  protected $resultsKey;
65 66

  /**
67
   * Stores the cache ID used for the output cache, once generateOutputKey() got
68 69 70
   * executed.
   *
   * @var string
71
   *
72
   * @see \Drupal\views\Plugin\views\cache\CachePluginBase::generateOutputKey()
73
   */
74
  protected $outputKey;
75

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * Constructs a CachePluginBase object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, RendererInterface $renderer) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->renderer = $renderer;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('renderer')
    );
  }

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
  /**
   * Returns the outputKey property.
   *
   * @return string
   *   The outputKey property.
   */
  public function getOutputKey() {
    return $this->outputKey;
  }

  /**
   * Returns the resultsKey property.
   *
   * @return string
   *   The resultsKey property.
   */
  public function getResultsKey() {
    return $this->resultsKey;
  }

merlinofchaos's avatar
merlinofchaos committed
133 134 135 136
  /**
   * Return a string to display as the clickable title for the
   * access control.
   */
137
  public function summaryTitle() {
138
    return $this->t('Unknown');
merlinofchaos's avatar
merlinofchaos committed
139 140 141 142 143 144 145 146 147 148
  }

  /**
   * Determine the expiration time of the cache type, or NULL if no expire.
   *
   * Plugins must override this to implement expiration.
   *
   * @param $type
   *   The cache type, either 'query', 'result' or 'output'.
   */
149 150
  protected function cacheExpire($type) {
  }
merlinofchaos's avatar
merlinofchaos committed
151

152 153 154 155 156 157 158 159 160
  /**
   * Determine expiration time in the cache table of the cache type
   * or CACHE_PERMANENT if item shouldn't be removed automatically from cache.
   *
   * Plugins must override this to implement expiration in the cache table.
   *
   * @param $type
   *   The cache type, either 'query', 'result' or 'output'.
   */
161
  protected function cacheSetExpire($type) {
162
    return Cache::PERMANENT;
merlinofchaos's avatar
merlinofchaos committed
163 164 165 166 167 168 169
  }

  /**
   * Save data to the cache.
   *
   * A plugin should override this to provide specialized caching behavior.
   */
170
  public function cacheSet($type) {
merlinofchaos's avatar
merlinofchaos committed
171 172 173 174 175 176
    switch ($type) {
      case 'query':
        // Not supported currently, but this is certainly where we'd put it.
        break;
      case 'results':
        $data = array(
177
          'result' => $this->prepareViewResult($this->view->result),
merlinofchaos's avatar
merlinofchaos committed
178
          'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
179
          'current_page' => $this->view->getCurrentPage(),
merlinofchaos's avatar
merlinofchaos committed
180
        );
181
        \Drupal::cache($this->resultsBin)->set($this->generateResultsKey(), $data, $this->cacheSetExpire($type), $this->getCacheTags());
merlinofchaos's avatar
merlinofchaos committed
182 183
        break;
      case 'output':
184 185
        $this->renderer->render($this->view->display_handler->output);
        $this->storage = $this->renderer->getCacheableRenderArray($this->view->display_handler->output);
186
        \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), $this->getCacheTags());
merlinofchaos's avatar
merlinofchaos committed
187 188 189 190 191 192 193 194 195
        break;
    }
  }

  /**
   * Retrieve data from the cache.
   *
   * A plugin should override this to provide specialized caching behavior.
   */
196
  public function cacheGet($type) {
197
    $cutoff = $this->cacheExpire($type);
merlinofchaos's avatar
merlinofchaos committed
198 199 200 201 202 203 204
    switch ($type) {
      case 'query':
        // Not supported currently, but this is certainly where we'd put it.
        return FALSE;
      case 'results':
        // Values to set: $view->result, $view->total_rows, $view->execute_time,
        // $view->current_page.
205
        if ($cache = \Drupal::cache($this->resultsBin)->get($this->generateResultsKey())) {
merlinofchaos's avatar
merlinofchaos committed
206 207
          if (!$cutoff || $cache->created > $cutoff) {
            $this->view->result = $cache->data['result'];
208 209
            // Load entities for each result.
            $this->view->query->loadEntities($this->view->result);
merlinofchaos's avatar
merlinofchaos committed
210
            $this->view->total_rows = $cache->data['total_rows'];
211
            $this->view->setCurrentPage($cache->data['current_page']);
merlinofchaos's avatar
merlinofchaos committed
212 213 214 215 216 217
            $this->view->execute_time = 0;
            return TRUE;
          }
        }
        return FALSE;
      case 'output':
218
        if ($cache = \Drupal::cache($this->outputBin)->get($this->generateOutputKey())) {
merlinofchaos's avatar
merlinofchaos committed
219 220
          if (!$cutoff || $cache->created > $cutoff) {
            $this->storage = $cache->data;
221 222 223 224
            $this->view->display_handler->output = $this->storage;
            $this->view->element['#attached'] = &$this->view->display_handler->output['#attached'];
            $this->view->element['#cache']['tags'] = &$this->view->display_handler->output['#cache']['tags'];
            $this->view->element['#post_render_cache'] = &$this->view->display_handler->output['#post_render_cache'];
merlinofchaos's avatar
merlinofchaos committed
225 226 227 228 229 230 231 232 233 234 235 236 237
            return TRUE;
          }
        }
        return FALSE;
    }
  }

  /**
   * Clear out cached data for a view.
   *
   * We're just going to nuke anything related to the view, regardless of display,
   * to be sure that we catch everything. Maybe that's a bad idea.
   */
238
  public function cacheFlush() {
239
    Cache::invalidateTags($this->view->storage->getCacheTags());
merlinofchaos's avatar
merlinofchaos committed
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
  }

  /**
   * Post process any rendered data.
   *
   * This can be valuable to be able to cache a view and still have some level of
   * dynamic output. In an ideal world, the actual output will include HTML
   * comment based tokens, and then the post process can replace those tokens.
   *
   * Example usage. If it is known that the view is a node view and that the
   * primary field will be a nid, you can do something like this:
   *
   * <!--post-FIELD-NID-->
   *
   * And then in the post render, create an array with the text that should
   * go there:
   *
   * strtr($output, array('<!--post-FIELD-1-->', 'output for FIELD of nid 1');
   *
   * All of the cached result data will be available in $view->result, as well,
   * so all ids used in the query should be discoverable.
   */
262
  public function postRender(&$output) { }
merlinofchaos's avatar
merlinofchaos committed
263 264

  /**
265
   * Start caching the html head.
merlinofchaos's avatar
merlinofchaos committed
266
   */
267
  public function cacheStart() { }
merlinofchaos's avatar
merlinofchaos committed
268

269 270 271 272 273 274 275 276
  /**
   * Calculates and sets a cache ID used for the result cache.
   *
   * @return string
   *   The generated cache ID.
   */
  public function generateResultsKey() {
    if (!isset($this->resultsKey)) {
merlinofchaos's avatar
merlinofchaos committed
277 278
      $build_info = $this->view->build_info;

279
      foreach (array('query', 'count_query') as $index) {
merlinofchaos's avatar
merlinofchaos committed
280 281
        // If the default query back-end is used generate SQL query strings from
        // the query objects.
282
        if ($build_info[$index] instanceof Select) {
merlinofchaos's avatar
merlinofchaos committed
283 284 285 286 287
          $query = clone $build_info[$index];
          $query->preExecute();
          $build_info[$index] = (string)$query;
        }
      }
288
      $user = \Drupal::currentUser();
merlinofchaos's avatar
merlinofchaos committed
289 290
      $key_data = array(
        'build_info' => $build_info,
291
        'roles' => $user->getRoles(),
292
        'super-user' => $user->id() == 1, // special caching for super user.
293
        'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
merlinofchaos's avatar
merlinofchaos committed
294 295 296
        'base_url' => $GLOBALS['base_url'],
      );
      foreach (array('exposed_info', 'page', 'sort', 'order', 'items_per_page', 'offset') as $key) {
297 298
        if ($this->view->getRequest()->query->has($key)) {
          $key_data[$key] = $this->view->getRequest()->query->get($key);
merlinofchaos's avatar
merlinofchaos committed
299 300 301
        }
      }

302
      $this->resultsKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':results:' . hash('sha256', serialize($key_data));
merlinofchaos's avatar
merlinofchaos committed
303 304
    }

305
    return $this->resultsKey;
merlinofchaos's avatar
merlinofchaos committed
306 307
  }

308 309 310 311 312 313 314 315
  /**
   * Calculates and sets a cache ID used for the output cache.
   *
   * @return string
   *   The generated cache ID.
   */
  public function generateOutputKey() {
    if (!isset($this->outputKey)) {
316
      $user = \Drupal::currentUser();
merlinofchaos's avatar
merlinofchaos committed
317 318
      $key_data = array(
        'result' => $this->view->result,
319
        'roles' => $user->getRoles(),
320
        'super-user' => $user->id() == 1, // special caching for super user.
321
        'theme' => \Drupal::theme()->getActiveTheme()->getName(),
322
        'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
merlinofchaos's avatar
merlinofchaos committed
323 324 325
        'base_url' => $GLOBALS['base_url'],
      );

326
      $this->outputKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':output:' . hash('sha256', serialize($key_data));
merlinofchaos's avatar
merlinofchaos committed
327 328
    }

329
    return $this->outputKey;
merlinofchaos's avatar
merlinofchaos committed
330
  }
331

332 333 334
  /**
   * Gets an array of cache tags for the current view.
   *
335 336
   * @return string[]
   *   An array of cache tags based on the current view.
337 338
   */
  protected function getCacheTags() {
339
    $tags = $this->view->storage->getCacheTags();
340 341 342 343

    $entity_information = $this->view->query->getEntityTableInfo();

    if (!empty($entity_information)) {
344
      // Add the list cache tags for each entity type used by this view.
345
      foreach (array_keys($entity_information) as $entity_type) {
346
        $tags = Cache::mergeTags($tags, \Drupal::entityManager()->getDefinition($entity_type)->getListCacheTags());
347 348 349
      }
    }

350
    return $tags;
351 352
  }

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
  /**
   * Prepares the view result before putting it into cache.
   *
   * @param \Drupal\views\ResultRow[] $result
   *   The result containing loaded entities.
   *
   * @return \Drupal\views\ResultRow[] $result
   *   The result without loaded entities.
   */
  protected function prepareViewResult(array $result) {
    $return = [];

    // Clone each row object and remove any loaded entities, to keep the
    // original result rows intact.
    foreach ($result as $key => $row) {
      $clone = clone $row;
      $clone->resetEntityData();
      $return[$key] = $clone;
    }

    return $return;
  }

376 377 378 379 380 381 382 383 384 385 386
  /**
   * Alters the cache metadata of a display upon saving a view.
   *
   * @param bool $is_cacheable
   *   Whether the display is cacheable.
   * @param string[] $cache_contexts
   *   The cache contexts the display varies by.
   */
  public function alterCacheMetadata(&$is_cacheable, array &$cache_contexts) {
  }

merlinofchaos's avatar
merlinofchaos committed
387 388 389 390 391
}

/**
 * @}
 */