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