Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

PdoSessionHandler.php

Zuletzt modifiziert: 09.10.2024, 12:59 - Dateigröße: 9.53 KiB


 001  <?php
 002   
 003  /*
 004   * This file is part of the Symfony package.
 005   *
 006   * (c) Fabien Potencier <fabien@symfony.com>
 007   *
 008   * For the full copyright and license information, please view the LICENSE
 009   * file that was distributed with this source code.
 010   */
 011   
 012  namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
 013   
 014  /**
 015   * Session handler using a PDO connection to read and write data.
 016   *
 017   * Session data is a binary string that can contain non-printable characters like the null byte.
 018   * For this reason this handler base64 encodes the data to be able to save it in a character column.
 019   *
 020   * This version of the PdoSessionHandler does NOT implement locking. So concurrent requests to the
 021   * same session can result in data loss due to race conditions.
 022   *
 023   * @author Fabien Potencier <fabien@symfony.com>
 024   * @author Michael Williams <michael.williams@funsational.com>
 025   * @author Tobias Schultze <http://tobion.de>
 026   */
 027  class PdoSessionHandler implements \SessionHandlerInterface
 028  {
 029      /**
 030       * @var \PDO PDO instance
 031       */
 032      private $pdo;
 033   
 034      /**
 035       * @var string Table name
 036       */
 037      private $table;
 038   
 039      /**
 040       * @var string Column for session id
 041       */
 042      private $idCol;
 043   
 044      /**
 045       * @var string Column for session data
 046       */
 047      private $dataCol;
 048   
 049      /**
 050       * @var string Column for timestamp
 051       */
 052      private $timeCol;
 053   
 054      /**
 055       * Constructor.
 056       *
 057       * List of available options:
 058       *  * db_table: The name of the table [required]
 059       *  * db_id_col: The column where to store the session id [default: sess_id]
 060       *  * db_data_col: The column where to store the session data [default: sess_data]
 061       *  * db_time_col: The column where to store the timestamp [default: sess_time]
 062       *
 063       * @param \PDO  $pdo       A \PDO instance
 064       * @param array $dbOptions An associative array of DB options
 065       *
 066       * @throws \InvalidArgumentException When "db_table" option is not provided
 067       */
 068      public function __construct(\PDO $pdo, array $dbOptions = array())
 069      {
 070          if (!array_key_exists('db_table', $dbOptions)) {
 071              throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.');
 072          }
 073          if (\PDO::ERRMODE_EXCEPTION !== $pdo->getAttribute(\PDO::ATTR_ERRMODE)) {
 074              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__));
 075          }
 076          $this->pdo = $pdo;
 077          $dbOptions = array_merge(array(
 078              'db_id_col'   => 'sess_id',
 079              'db_data_col' => 'sess_data',
 080              'db_time_col' => 'sess_time',
 081          ), $dbOptions);
 082   
 083          $this->table = $dbOptions['db_table'];
 084          $this->idCol = $dbOptions['db_id_col'];
 085          $this->dataCol = $dbOptions['db_data_col'];
 086          $this->timeCol = $dbOptions['db_time_col'];
 087      }
 088   
 089      /**
 090       * {@inheritdoc}
 091       */
 092      public function open($savePath, $sessionName)
 093      {
 094          return true;
 095      }
 096   
 097      /**
 098       * {@inheritdoc}
 099       */
 100      public function close()
 101      {
 102          return true;
 103      }
 104   
 105      /**
 106       * {@inheritdoc}
 107       */
 108      public function destroy($sessionId)
 109      {
 110          // delete the record associated with this id
 111          $sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
 112   
 113          try {
 114              $stmt = $this->pdo->prepare($sql);
 115              $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 116              $stmt->execute();
 117          } catch (\PDOException $e) {
 118              throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete a session: %s', $e->getMessage()), 0, $e);
 119          }
 120   
 121          return true;
 122      }
 123   
 124      /**
 125       * {@inheritdoc}
 126       */
 127      public function gc($maxlifetime)
 128      {
 129          // delete the session records that have expired
 130          $sql = "DELETE FROM $this->table WHERE $this->timeCol < :time";
 131   
 132          try {
 133              $stmt = $this->pdo->prepare($sql);
 134              $stmt->bindValue(':time', time() - $maxlifetime, \PDO::PARAM_INT);
 135              $stmt->execute();
 136          } catch (\PDOException $e) {
 137              throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete expired sessions: %s', $e->getMessage()), 0, $e);
 138          }
 139   
 140          return true;
 141      }
 142   
 143      /**
 144       * {@inheritdoc}
 145       */
 146      public function read($sessionId)
 147      {
 148          $sql = "SELECT $this->dataCol FROM $this->table WHERE $this->idCol = :id";
 149   
 150          try {
 151              $stmt = $this->pdo->prepare($sql);
 152              $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 153              $stmt->execute();
 154   
 155              // We use fetchAll instead of fetchColumn to make sure the DB cursor gets closed
 156              $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
 157   
 158              if ($sessionRows) {
 159                  return base64_decode($sessionRows[0][0]);
 160              }
 161   
 162              return '';
 163          } catch (\PDOException $e) {
 164              throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
 165          }
 166      }
 167   
 168      /**
 169       * {@inheritdoc}
 170       */
 171      public function write($sessionId, $data)
 172      {
 173          $encoded = base64_encode($data);
 174   
 175          try {
 176              // We use a single MERGE SQL query when supported by the database.
 177              $mergeSql = $this->getMergeSql();
 178   
 179              if (null !== $mergeSql) {
 180                  $mergeStmt = $this->pdo->prepare($mergeSql);
 181                  $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 182                  $mergeStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
 183                  $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
 184                  $mergeStmt->execute();
 185   
 186                  return true;
 187              }
 188   
 189              $updateStmt = $this->pdo->prepare(
 190                  "UPDATE $this->table SET $this->dataCol = :data, $this->timeCol = :time WHERE $this->idCol = :id"
 191              );
 192              $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 193              $updateStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
 194              $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
 195              $updateStmt->execute();
 196   
 197              // When MERGE is not supported, like in Postgres, we have to use this approach that can result in
 198              // duplicate key errors when the same session is written simultaneously. We can just catch such an
 199              // error and re-execute the update. This is similar to a serializable transaction with retry logic
 200              // on serialization failures but without the overhead and without possible false positives due to
 201              // longer gap locking.
 202              if (!$updateStmt->rowCount()) {
 203                  try {
 204                      $insertStmt = $this->pdo->prepare(
 205                          "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"
 206                      );
 207                      $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
 208                      $insertStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
 209                      $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
 210                      $insertStmt->execute();
 211                  } catch (\PDOException $e) {
 212                      // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
 213                      if (0 === strpos($e->getCode(), '23')) {
 214                          $updateStmt->execute();
 215                      } else {
 216                          throw $e;
 217                      }
 218                  }
 219              }
 220          } catch (\PDOException $e) {
 221              throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
 222          }
 223   
 224          return true;
 225      }
 226   
 227      /**
 228       * Returns a merge/upsert (i.e. insert or update) SQL query when supported by the database.
 229       *
 230       * @return string|null The SQL string or null when not supported
 231       */
 232      private function getMergeSql()
 233      {
 234          $driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
 235   
 236          switch ($driver) {
 237              case 'mysql':
 238                  return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
 239                      "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)";
 240              case 'oci':
 241                  // DUAL is Oracle specific dummy table
 242                  return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ".
 243                      "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
 244                      "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time";
 245              case 'sqlsrv' === $driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
 246                  // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
 247                  // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
 248                  return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ".
 249                      "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
 250                      "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;";
 251              case 'sqlite':
 252                  return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)";
 253          }
 254      }
 255  }
 256