You are here

class SessionBasedTempStore in Session Based Temporary Storage 8

Stores and retrieves temporary data for a given owner.

A SessionBasedTempStore can be used as like PrivateTempStore to make temporary, non-cache data available across requests. The data for the PrivateTempStore is stored in one key/value collection. SessionBasedTempStore data expires automatically after a given timeframe.

The SessionBasedTempStore differs from the PrivateTempStore in that it can store data based on the user session but without Drupal cookie session. It means that you can use this storage to save data for anonymous user without breaking such things like Varnish.

Hierarchy

Expanded class hierarchy of SessionBasedTempStore

File

src/SessionBasedTempStore.php, line 24

Namespace

Drupal\session_based_temp_store
View source
class SessionBasedTempStore {

  /**
   * The key/value storage object used for this data.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
   */
  protected $storage;

  /**
   * The lock object used for this data.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface
   */
  protected $lockBackend;

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

  /**
   * The session configuration.
   *
   * @var \Drupal\Core\Session\SessionConfigurationInterface
   */
  protected $sessionConfiguration;

  /**
   * The time to live for items in seconds.
   *
   * By default, data is stored for one week (604800 seconds) before expiring.
   *
   * @var int
   */
  protected $expire;

  /**
   * The path on the server in which the cookie will be available on.
   *
   * By default the value is to '/'.
   *
   * @var string
   */
  protected $path;

  /**
   * The name of a cookie which will hold the storage ID.
   *
   * By default the value is to 'SESSbasedTempStoreId'.
   *
   * @var string
   */
  protected $cookieName;

  /**
   * Constructs a new object for accessing data from a key/value store.
   *
   * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $storage
   *   The key/value storage object used for this data. Each storage object
   *   represents a particular collection of data and will contain any number
   *   of key/value pairs.
   * @param \Drupal\Core\Lock\LockBackendInterface $lock_backend
   *   The lock object used for this data.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
   *   The session configuration interface.
   * @param string $cookie_name
   *   The name of a cookie which will hold the storage ID.
   * @param int $expire
   *   The time to live for items, in seconds.
   * @param string $path
   *   The path on the server in which the cookie will be available on.
   *   If set to '/', the cookie will be available within the entire domain.
   *   If set to '/foo/', the cookie will only be available
   *   within the /foo/ directory and all sub-directories such as /foo/bar/
   *   of domain. By default the value is to '/'.
   */
  public function __construct(KeyValueStoreExpirableInterface $storage, LockBackendInterface $lock_backend, RequestStack $request_stack, SessionConfigurationInterface $session_configuration, string $cookie_name, $expire = 604800, $path = '/') {
    $this->storage = $storage;
    $this->lockBackend = $lock_backend;
    $this->requestStack = $request_stack;
    $this->sessionConfiguration = $session_configuration;
    $this->cookieName = $cookie_name;
    $this->expire = $expire;
    $this->path = $path;
  }

  /**
   * Retrieves a value from this PrivateTempStore for a given key.
   *
   * @param string $key
   *   The key of the data to retrieve.
   *
   * @return mixed
   *   The data associated with the key, or NULL if the key does not exist.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  public function get($key) {
    $key = $this
      ->createkey($key);
    if (($object = $this->storage
      ->get($key)) && $object->owner == $this
      ->getOwner()) {
      return $object->data;
    }
  }

  /**
   * Retrieves an array of all values from this PrivateTempStore.
   *
   * @return array
   *   The array of key => value data or empty array.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  public function getAll() {
    $values = [];
    $owner = $this
      ->getOwner();
    $objects = $this->storage
      ->getAll();
    if (!empty($objects)) {
      foreach ($objects as $key => $object) {
        if ($object->owner == $owner) {
          $key = explode(':', $key);
          $values[$key[1]] = $object->data;
        }
      }
    }
    return $values;
  }

  /**
   * Stores a particular key/value pair in this PrivateTempStore.
   *
   * @param string $key
   *   The key of the data to store.
   * @param mixed $value
   *   The data to store.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   *   Thrown when a lock for the backend storage could not be acquired.
   */
  public function set($key, $value) {
    $key = $this
      ->createkey($key);
    if (!$this->lockBackend
      ->acquire($key)) {
      $this->lockBackend
        ->wait($key);
      if (!$this->lockBackend
        ->acquire($key)) {
        throw new TempStoreException("Couldn't acquire lock to update item '{$key}' in '{$this->storage->getCollectionName()}' session based temporary storage.");
      }
    }
    $value = (object) [
      'owner' => $this
        ->getOwner(),
      'data' => $value,
      'updated' => (int) $this->requestStack
        ->getMasterRequest()->server
        ->get('REQUEST_TIME'),
    ];

    // If the global expiration time is set to 0 (expire at the end of the session),
    // Let's set the DB storage entry expiration time to 24 hours.
    $expire = $this->expire === 0 ? 86400 : $this->expire;
    $this->storage
      ->setWithExpire($key, $value, $expire);
    $this->lockBackend
      ->release($key);
  }

  /**
   * Returns the metadata associated with a particular key/value pair.
   *
   * @param string $key
   *   The key of the data to store.
   *
   * @return mixed
   *   An object with the owner and updated time if the key has a value, or
   *   NULL otherwise.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  public function getMetadata($key) {
    $key = $this
      ->createkey($key);

    // Fetch the key/value pair and its metadata.
    $object = $this->storage
      ->get($key);
    if ($object) {

      // Don't keep the data itself in memory.
      unset($object->data);
      return $object;
    }
  }

  /**
   * Deletes data from the store for a given key and releases the lock on it.
   *
   * @param string $key
   *   The key of the data to delete.
   *
   * @return bool
   *   TRUE if the object was deleted or does not exist, FALSE if it exists but
   *   is not owned by $this->owner.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   *   Thrown when a lock for the backend storage could not be acquired.
   */
  public function delete($key) {
    $key = $this
      ->createkey($key);
    if (!($object = $this->storage
      ->get($key))) {
      return TRUE;
    }
    elseif ($object->owner != $this
      ->getOwner()) {
      return FALSE;
    }
    if (!$this->lockBackend
      ->acquire($key)) {
      $this->lockBackend
        ->wait($key);
      if (!$this->lockBackend
        ->acquire($key)) {
        throw new TempStoreException("Couldn't acquire lock to delete item '{$key}' from '{$this->storage->getCollectionName()}' session based temporary storage.");
      }
    }
    $this->storage
      ->delete($key);
    $this->lockBackend
      ->release($key);
    return TRUE;
  }

  /**
   * Deletes all data from the store for the current collection and owner.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  public function deleteAll() {
    $keys = [];
    foreach ($this
      ->getAll() as $key => $value) {
      $keys[] = $this
        ->createkey($key);
    }
    $this->storage
      ->deleteMultiple($keys);
  }

  /**
   * Gets the current owner based on the current session ID.
   *
   * @return string
   *   The owner.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   *   Thrown when headers have been already send.
   */
  protected function getOwner() {
    if (empty($_COOKIE[$this->cookieName])) {

      // Since security is not a problem, let's keep it short.
      $session_store_id = mb_substr(session_id(), 0, 12);
      $request = $this->requestStack
        ->getCurrentRequest();
      $session_options = $this->sessionConfiguration
        ->getOptions($request);

      // .localhost causes problems.
      $cookie_domain = $session_options['cookie_domain'] == '.localhost' ? ini_get('session.cookie_domain') : $session_options['cookie_domain'];

      // If the site is accessed via SSL, ensure that the cookie is issued
      // with the secure flag.
      $secure = $request
        ->isSecure();

      // setcookie() can only be called when headers are not yet sent.
      if (!headers_sent()) {
        setcookie($this->cookieName, $session_store_id, $this
          ->expirationTime(), $this->path, $cookie_domain, $secure, TRUE);
      }
      else {
        throw new TempStoreException("Couldn't set cookie " . $this->cookieName . " in session based temporary storage. Headers have been already sent.");
      }

      // When sessionStoreId() is called multiple times
      // and there is no $_COOKIE[$this->cookieName] yet.
      // The multiple identical Set-Cookie headers are sent to the client.
      // So we need to set $_COOKIE explicitly.
      $_COOKIE[$this->cookieName] = $session_store_id;
    }
    else {
      $session_store_id = $_COOKIE[$this->cookieName];
    }
    return $session_store_id;
  }

  /**
   * Ensures that the key is unique for a user.
   *
   * @param string $key
   *   The key.
   *
   * @return string
   *   The unique key for the user.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  protected function createkey($key) {
    return $this
      ->getOwner() . ':' . $key;
  }

  /**
   * Returns the date/time that the session store will expire.
   *
   * @return int
   *   UNIX time stamp.
   */
  protected function expirationTime() {

    // Allow the cookie to expire at the end of the session.
    if ($this->expire === 0) {
      return $this->expire;
    }

    // Otherwise set the specific expiration time passed in the argument.
    $request_time = (int) $this->requestStack
      ->getMasterRequest()->server
      ->get('REQUEST_TIME');
    return $request_time + $this->expire;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SessionBasedTempStore::$cookieName protected property The name of a cookie which will hold the storage ID.
SessionBasedTempStore::$expire protected property The time to live for items in seconds.
SessionBasedTempStore::$lockBackend protected property The lock object used for this data.
SessionBasedTempStore::$path protected property The path on the server in which the cookie will be available on.
SessionBasedTempStore::$requestStack protected property The request stack.
SessionBasedTempStore::$sessionConfiguration protected property The session configuration.
SessionBasedTempStore::$storage protected property The key/value storage object used for this data.
SessionBasedTempStore::createkey protected function Ensures that the key is unique for a user.
SessionBasedTempStore::delete public function Deletes data from the store for a given key and releases the lock on it.
SessionBasedTempStore::deleteAll public function Deletes all data from the store for the current collection and owner.
SessionBasedTempStore::expirationTime protected function Returns the date/time that the session store will expire.
SessionBasedTempStore::get public function Retrieves a value from this PrivateTempStore for a given key.
SessionBasedTempStore::getAll public function Retrieves an array of all values from this PrivateTempStore.
SessionBasedTempStore::getMetadata public function Returns the metadata associated with a particular key/value pair.
SessionBasedTempStore::getOwner protected function Gets the current owner based on the current session ID.
SessionBasedTempStore::set public function Stores a particular key/value pair in this PrivateTempStore.
SessionBasedTempStore::__construct public function Constructs a new object for accessing data from a key/value store.