You are here

TimestampCacheTagsChecksum.php in Memcache API and Integration 8.2

File

src/Cache/TimestampCacheTagsChecksum.php
View source
<?php

namespace Drupal\memcache\Cache;

use Drupal\Core\Cache\CacheTagsChecksumInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\memcache\Invalidator\TimestampInvalidatorInterface;

/**
 * Cache tags invalidations checksum implementation by timestamp invalidation.
 */
class TimestampCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface {

  /**
   * The timestamp invalidator object.
   *
   * @var \Drupal\memcache\Invalidator\TimestampInvalidatorInterface
   */
  protected $invalidator;

  /**
   * Contains already loaded cache invalidations from the backend.
   *
   * @var array
   */
  protected $tagCache = [];

  /**
   * A list of tags that have already been invalidated in this request.
   *
   * Used to prevent the invalidation of the same cache tag multiple times.
   *
   * @var array
   */
  protected $invalidatedTags = [];

  /**
   * Constructs a TimestampCacheTagsChecksum object.
   *
   * @param \Drupal\memcache\Invalidator\TimestampInvalidatorInterface $invalidator
   *   The timestamp invalidator object.
   */
  public function __construct(TimestampInvalidatorInterface $invalidator) {
    $this->invalidator = $invalidator;
  }

  /**
   * {@inheritdoc}
   */
  public function invalidateTags(array $tags) {
    foreach ($tags as $tag) {

      // @todo Revisit this behavior and determine a better way to handle.
      // Only invalidate tags once per request unless they are written again.
      if (isset($this->invalidatedTags[$tag])) {
        continue;
      }
      $this->invalidatedTags[$tag] = TRUE;
      $this->tagCache[$tag] = $this->invalidator
        ->invalidateTimestamp($tag);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getCurrentChecksum(array $tags) {

    // @todo Revisit the invalidatedTags hack.
    // Remove tags that were already invalidated during this request from the
    // static caches so that another invalidation can occur later in the same
    // request. Without that, written cache items would not be invalidated
    // correctly.
    foreach ($tags as $tag) {
      unset($this->invalidatedTags[$tag]);
    }

    // Taking the minimum of the current timestamp and the checksum is used to
    // ensure that items that are not valid yet are identified properly as not
    // valid. The checksum will change continuously until the item is valid,
    // at which point the checksum will match and freeze at that value.
    return min($this->invalidator
      ->getCurrentTimestamp(), $this
      ->calculateChecksum($tags));
  }

  /**
   * {@inheritdoc}
   */
  public function isValid($checksum, array $tags) {
    if (empty($tags)) {

      // If there weren't any tags, the checksum should always be 0 or FALSE.
      return $checksum == 0;
    }
    return $checksum == $this
      ->calculateChecksum($tags);
  }

  /**
   * Calculates the current checksum for a given set of tags.
   *
   * @param array $tags
   *   The array of tags to calculate the checksum for.
   *
   * @return int
   *   The calculated checksum.
   */
  protected function calculateChecksum(array $tags) {
    $query_tags = array_diff($tags, array_keys($this->tagCache));
    if ($query_tags) {
      $backend_tags = $this->invalidator
        ->getLastInvalidationTimestamps($query_tags);
      $this->tagCache += $backend_tags;
      $invalid = array_diff($query_tags, array_keys($backend_tags));
      if (!empty($invalid)) {

        // Invalidate any missing tags now. This is necessary because we cannot
        // zero-optimize our tag list -- we can't tell the difference between
        // a tag that has never been invalidated and a tag that was
        // garbage-collected by the backend!
        //
        // This behavioral difference is the main change that allows us to use
        // an unreliable backend to track cache tag invalidation.
        //
        // Invalidating the tag will cause it to start being tracked, so it can
        // be matched against the checksums stored on items.
        // All items cached after that point with the tag will end up with
        // a valid checksum, and all items cached before that point with the tag
        // will have an invalid checksum, because missing invalidations will
        // keep moving forward in time as they get garbage collected and are
        // re-invalidated.
        //
        // The main effect of all this is that a tag going missing
        // will automatically cause the cache items tagged with it to no longer
        // have the correct checksum.
        foreach ($invalid as $invalid_tag) {
          $this->invalidator
            ->invalidateTimestamp($invalid_tag);
        }
      }
    }

    // The checksum is equal to the *most recent* invalidation of an applicable
    // tag. If the item is untagged, the checksum is always 0.
    return max([
      0,
    ] + array_intersect_key($this->tagCache, array_flip($tags)));
  }

  /**
   * {@inheritdoc}
   */
  public function reset() {
    $this->tagCache = [];
    $this->invalidatedTags = [];
  }

}

Classes

Namesort descending Description
TimestampCacheTagsChecksum Cache tags invalidations checksum implementation by timestamp invalidation.