[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/vendor/symfony/http-foundation/Session/Storage/Handler/ -> LegacyPdoSessionHandler.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of the Symfony package.
   5   *
   6   * (c) Fabien Potencier <fabien@symfony.com>
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
  13  
  14  @trigger_error('The '.__NAMESPACE__.'\LegacyPdoSessionHandler class is deprecated since Symfony 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler class instead.', E_USER_DEPRECATED);
  15  
  16  /**
  17   * Session handler using a PDO connection to read and write data.
  18   *
  19   * Session data is a binary string that can contain non-printable characters like the null byte.
  20   * For this reason this handler base64 encodes the data to be able to save it in a character column.
  21   *
  22   * This version of the PdoSessionHandler does NOT implement locking. So concurrent requests to the
  23   * same session can result in data loss due to race conditions.
  24   *
  25   * @author Fabien Potencier <fabien@symfony.com>
  26   * @author Michael Williams <michael.williams@funsational.com>
  27   * @author Tobias Schultze <http://tobion.de>
  28   *
  29   * @deprecated since version 2.6, to be removed in 3.0. Use
  30   *             {@link PdoSessionHandler} instead.
  31   */
  32  class LegacyPdoSessionHandler implements \SessionHandlerInterface
  33  {
  34      private $pdo;
  35  
  36      /**
  37       * @var string Table name
  38       */
  39      private $table;
  40  
  41      /**
  42       * @var string Column for session id
  43       */
  44      private $idCol;
  45  
  46      /**
  47       * @var string Column for session data
  48       */
  49      private $dataCol;
  50  
  51      /**
  52       * @var string Column for timestamp
  53       */
  54      private $timeCol;
  55  
  56      /**
  57       * Constructor.
  58       *
  59       * List of available options:
  60       *  * db_table: The name of the table [required]
  61       *  * db_id_col: The column where to store the session id [default: sess_id]
  62       *  * db_data_col: The column where to store the session data [default: sess_data]
  63       *  * db_time_col: The column where to store the timestamp [default: sess_time]
  64       *
  65       * @param \PDO  $pdo       A \PDO instance
  66       * @param array $dbOptions An associative array of DB options
  67       *
  68       * @throws \InvalidArgumentException When "db_table" option is not provided
  69       */
  70      public function __construct(\PDO $pdo, array $dbOptions = array())
  71      {
  72          if (!array_key_exists('db_table', $dbOptions)) {
  73              throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.');
  74          }
  75          if (\PDO::ERRMODE_EXCEPTION !== $pdo->getAttribute(\PDO::ATTR_ERRMODE)) {
  76              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__));
  77          }
  78  
  79          $this->pdo = $pdo;
  80          $dbOptions = array_merge(array(
  81              'db_id_col' => 'sess_id',
  82              'db_data_col' => 'sess_data',
  83              'db_time_col' => 'sess_time',
  84          ), $dbOptions);
  85  
  86          $this->table = $dbOptions['db_table'];
  87          $this->idCol = $dbOptions['db_id_col'];
  88          $this->dataCol = $dbOptions['db_data_col'];
  89          $this->timeCol = $dbOptions['db_time_col'];
  90      }
  91  
  92      /**
  93       * {@inheritdoc}
  94       */
  95      public function open($savePath, $sessionName)
  96      {
  97          return true;
  98      }
  99  
 100      /**
 101       * {@inheritdoc}
 102       */
 103      public function close()
 104      {
 105          return true;
 106      }
 107  
 108      /**
 109       * {@inheritdoc}
 110       */
 111      public function destroy($sessionId)
 112      {
 113          // delete the record associated with this id
 114          $sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
 115  
 116          try {
 117              $stmt = $this->pdo->prepare($sql);
 118              $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 119              $stmt->execute();
 120          } catch (\PDOException $e) {
 121              throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete a session: %s', $e->getMessage()), 0, $e);
 122          }
 123  
 124          return true;
 125      }
 126  
 127      /**
 128       * {@inheritdoc}
 129       */
 130      public function gc($maxlifetime)
 131      {
 132          // delete the session records that have expired
 133          $sql = "DELETE FROM $this->table WHERE $this->timeCol < :time";
 134  
 135          try {
 136              $stmt = $this->pdo->prepare($sql);
 137              $stmt->bindValue(':time', time() - $maxlifetime, \PDO::PARAM_INT);
 138              $stmt->execute();
 139          } catch (\PDOException $e) {
 140              throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete expired sessions: %s', $e->getMessage()), 0, $e);
 141          }
 142  
 143          return true;
 144      }
 145  
 146      /**
 147       * {@inheritdoc}
 148       */
 149      public function read($sessionId)
 150      {
 151          $sql = "SELECT $this->dataCol FROM $this->table WHERE $this->idCol = :id";
 152  
 153          try {
 154              $stmt = $this->pdo->prepare($sql);
 155              $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 156              $stmt->execute();
 157  
 158              // We use fetchAll instead of fetchColumn to make sure the DB cursor gets closed
 159              $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
 160  
 161              if ($sessionRows) {
 162                  return base64_decode($sessionRows[0][0]);
 163              }
 164  
 165              return '';
 166          } catch (\PDOException $e) {
 167              throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
 168          }
 169      }
 170  
 171      /**
 172       * {@inheritdoc}
 173       */
 174      public function write($sessionId, $data)
 175      {
 176          $encoded = base64_encode($data);
 177  
 178          try {
 179              // We use a single MERGE SQL query when supported by the database.
 180              $mergeSql = $this->getMergeSql();
 181  
 182              if (null !== $mergeSql) {
 183                  $mergeStmt = $this->pdo->prepare($mergeSql);
 184                  $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 185                  $mergeStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
 186                  $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
 187                  $mergeStmt->execute();
 188  
 189                  return true;
 190              }
 191  
 192              $updateStmt = $this->pdo->prepare(
 193                  "UPDATE $this->table SET $this->dataCol = :data, $this->timeCol = :time WHERE $this->idCol = :id"
 194              );
 195              $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 196              $updateStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
 197              $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
 198              $updateStmt->execute();
 199  
 200              // When MERGE is not supported, like in Postgres, we have to use this approach that can result in
 201              // duplicate key errors when the same session is written simultaneously. We can just catch such an
 202              // error and re-execute the update. This is similar to a serializable transaction with retry logic
 203              // on serialization failures but without the overhead and without possible false positives due to
 204              // longer gap locking.
 205              if (!$updateStmt->rowCount()) {
 206                  try {
 207                      $insertStmt = $this->pdo->prepare(
 208                          "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"
 209                      );
 210                      $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 211                      $insertStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
 212                      $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
 213                      $insertStmt->execute();
 214                  } catch (\PDOException $e) {
 215                      // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
 216                      if (0 === strpos($e->getCode(), '23')) {
 217                          $updateStmt->execute();
 218                      } else {
 219                          throw $e;
 220                      }
 221                  }
 222              }
 223          } catch (\PDOException $e) {
 224              throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
 225          }
 226  
 227          return true;
 228      }
 229  
 230      /**
 231       * Returns a merge/upsert (i.e. insert or update) SQL query when supported by the database.
 232       *
 233       * @return string|null The SQL string or null when not supported
 234       */
 235      private function getMergeSql()
 236      {
 237          $driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
 238  
 239          switch ($driver) {
 240              case 'mysql':
 241                  return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
 242                  "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)";
 243              case 'oci':
 244                  // DUAL is Oracle specific dummy table
 245                  return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ".
 246                  "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
 247                  "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time";
 248              case 'sqlsrv' === $driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
 249                  // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
 250                  // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
 251                  return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ".
 252                  "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
 253                  "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;";
 254              case 'sqlite':
 255                  return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)";
 256          }
 257      }
 258  
 259      /**
 260       * Return a PDO instance.
 261       *
 262       * @return \PDO
 263       */
 264      protected function getConnection()
 265      {
 266          return $this->pdo;
 267      }
 268  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1