PdoSessionHandler.php

Namespace

Symfony\Component\HttpFoundation\Session\Storage\Handler

File

drupal/core/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
View source
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;


/**
 * PdoSessionHandler.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Michael Williams <michael.williams@funsational.com>
 */
class PdoSessionHandler implements \SessionHandlerInterface {

  /**
   * @var \PDO PDO instance.
   */
  private $pdo;

  /**
   * @var array Database options.
   */
  private $dbOptions;

  /**
   * Constructor.
   *
   * List of available options:
   *  * db_table: The name of the table [required]
   *  * db_id_col: The column where to store the session id [default: sess_id]
   *  * db_data_col: The column where to store the session data [default: sess_data]
   *  * db_time_col: The column where to store the timestamp [default: sess_time]
   *
   * @param \PDO  $pdo       A \PDO instance
   * @param array $dbOptions An associative array of DB options
   *
   * @throws \InvalidArgumentException When "db_table" option is not provided
   */
  public function __construct(\PDO $pdo, array $dbOptions = array()) {
    if (!array_key_exists('db_table', $dbOptions)) {
      throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.');
    }
    if (\PDO::ERRMODE_EXCEPTION !== $pdo
      ->getAttribute(\PDO::ATTR_ERRMODE)) {
      throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
    }
    $this->pdo = $pdo;
    $this->dbOptions = array_merge(array(
      'db_id_col' => 'sess_id',
      'db_data_col' => 'sess_data',
      'db_time_col' => 'sess_time',
    ), $dbOptions);
  }

  /**
   * {@inheritDoc}
   */
  public function open($path, $name) {
    return true;
  }

  /**
   * {@inheritDoc}
   */
  public function close() {
    return true;
  }

  /**
   * {@inheritDoc}
   */
  public function destroy($id) {

    // get table/column
    $dbTable = $this->dbOptions['db_table'];
    $dbIdCol = $this->dbOptions['db_id_col'];

    // delete the record associated with this id
    $sql = "DELETE FROM {$dbTable} WHERE {$dbIdCol} = :id";
    try {
      $stmt = $this->pdo
        ->prepare($sql);
      $stmt
        ->bindParam(':id', $id, \PDO::PARAM_STR);
      $stmt
        ->execute();
    } catch (\PDOException $e) {
      throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e
        ->getMessage()), 0, $e);
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  public function gc($lifetime) {

    // get table/column
    $dbTable = $this->dbOptions['db_table'];
    $dbTimeCol = $this->dbOptions['db_time_col'];

    // delete the session records that have expired
    $sql = "DELETE FROM {$dbTable} WHERE {$dbTimeCol} < :time";
    try {
      $stmt = $this->pdo
        ->prepare($sql);
      $stmt
        ->bindValue(':time', time() - $lifetime, \PDO::PARAM_INT);
      $stmt
        ->execute();
    } catch (\PDOException $e) {
      throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e
        ->getMessage()), 0, $e);
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  public function read($id) {

    // get table/columns
    $dbTable = $this->dbOptions['db_table'];
    $dbDataCol = $this->dbOptions['db_data_col'];
    $dbIdCol = $this->dbOptions['db_id_col'];
    try {
      $sql = "SELECT {$dbDataCol} FROM {$dbTable} WHERE {$dbIdCol} = :id";
      $stmt = $this->pdo
        ->prepare($sql);
      $stmt
        ->bindParam(':id', $id, \PDO::PARAM_STR);
      $stmt
        ->execute();

      // it is recommended to use fetchAll so that PDO can close the DB cursor
      // we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777
      $sessionRows = $stmt
        ->fetchAll(\PDO::FETCH_NUM);
      if (count($sessionRows) == 1) {
        return base64_decode($sessionRows[0][0]);
      }

      // session does not exist, create it
      $this
        ->createNewSession($id);
      return '';
    } catch (\PDOException $e) {
      throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e
        ->getMessage()), 0, $e);
    }
  }

  /**
   * {@inheritDoc}
   */
  public function write($id, $data) {

    // get table/column
    $dbTable = $this->dbOptions['db_table'];
    $dbDataCol = $this->dbOptions['db_data_col'];
    $dbIdCol = $this->dbOptions['db_id_col'];
    $dbTimeCol = $this->dbOptions['db_time_col'];

    //session data can contain non binary safe characters so we need to encode it
    $encoded = base64_encode($data);
    try {
      $driver = $this->pdo
        ->getAttribute(\PDO::ATTR_DRIVER_NAME);
      if ('mysql' === $driver) {

        // MySQL would report $stmt->rowCount() = 0 on UPDATE when the data is left unchanged
        // it could result in calling createNewSession() whereas the session already exists in
        // the DB which would fail as the id is unique
        $stmt = $this->pdo
          ->prepare("INSERT INTO {$dbTable} ({$dbIdCol}, {$dbDataCol}, {$dbTimeCol}) VALUES (:id, :data, :time) " . "ON DUPLICATE KEY UPDATE {$dbDataCol} = VALUES({$dbDataCol}), {$dbTimeCol} = VALUES({$dbTimeCol})");
        $stmt
          ->bindParam(':id', $id, \PDO::PARAM_STR);
        $stmt
          ->bindParam(':data', $encoded, \PDO::PARAM_STR);
        $stmt
          ->bindValue(':time', time(), \PDO::PARAM_INT);
        $stmt
          ->execute();
      }
      elseif ('oci' === $driver) {
        $stmt = $this->pdo
          ->prepare("MERGE INTO {$dbTable} USING DUAL ON({$dbIdCol} = :id) " . "WHEN NOT MATCHED THEN INSERT ({$dbIdCol}, {$dbDataCol}, {$dbTimeCol}) VALUES (:id, :data, sysdate) " . "WHEN MATCHED THEN UPDATE SET {$dbDataCol} = :data WHERE {$dbIdCol} = :id");
        $stmt
          ->bindParam(':id', $id, \PDO::PARAM_STR);
        $stmt
          ->bindParam(':data', $encoded, \PDO::PARAM_STR);
        $stmt
          ->execute();
      }
      else {
        $stmt = $this->pdo
          ->prepare("UPDATE {$dbTable} SET {$dbDataCol} = :data, {$dbTimeCol} = :time WHERE {$dbIdCol} = :id");
        $stmt
          ->bindParam(':id', $id, \PDO::PARAM_STR);
        $stmt
          ->bindParam(':data', $encoded, \PDO::PARAM_STR);
        $stmt
          ->bindValue(':time', time(), \PDO::PARAM_INT);
        $stmt
          ->execute();
        if (!$stmt
          ->rowCount()) {

          // No session exists in the database to update. This happens when we have called
          // session_regenerate_id()
          $this
            ->createNewSession($id, $data);
        }
      }
    } catch (\PDOException $e) {
      throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e
        ->getMessage()), 0, $e);
    }
    return true;
  }

  /**
   * Creates a new session with the given $id and $data
   *
   * @param string $id
   * @param string $data
   *
   * @return boolean True.
   */
  private function createNewSession($id, $data = '') {

    // get table/column
    $dbTable = $this->dbOptions['db_table'];
    $dbDataCol = $this->dbOptions['db_data_col'];
    $dbIdCol = $this->dbOptions['db_id_col'];
    $dbTimeCol = $this->dbOptions['db_time_col'];
    $sql = "INSERT INTO {$dbTable} ({$dbIdCol}, {$dbDataCol}, {$dbTimeCol}) VALUES (:id, :data, :time)";

    //session data can contain non binary safe characters so we need to encode it
    $encoded = base64_encode($data);
    $stmt = $this->pdo
      ->prepare($sql);
    $stmt
      ->bindParam(':id', $id, \PDO::PARAM_STR);
    $stmt
      ->bindParam(':data', $encoded, \PDO::PARAM_STR);
    $stmt
      ->bindValue(':time', time(), \PDO::PARAM_INT);
    $stmt
      ->execute();
    return true;
  }

}

Classes

Namesort descending Description
PdoSessionHandler PdoSessionHandler.