[ Index ] |
PHP Cross Reference of phpBB-3.1.12-deutsch |
[Summary view] [Print] [Text view]
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 /** 15 * Session handler using a PDO connection to read and write data. 16 * 17 * Session data is a binary string that can contain non-printable characters like the null byte. 18 * For this reason this handler base64 encodes the data to be able to save it in a character column. 19 * 20 * This version of the PdoSessionHandler does NOT implement locking. So concurrent requests to the 21 * same session can result in data loss due to race conditions. 22 * 23 * @author Fabien Potencier <fabien@symfony.com> 24 * @author Michael Williams <michael.williams@funsational.com> 25 * @author Tobias Schultze <http://tobion.de> 26 */ 27 class PdoSessionHandler implements \SessionHandlerInterface 28 { 29 /** 30 * @var \PDO PDO instance 31 */ 32 private $pdo; 33 34 /** 35 * @var string Table name 36 */ 37 private $table; 38 39 /** 40 * @var string Column for session id 41 */ 42 private $idCol; 43 44 /** 45 * @var string Column for session data 46 */ 47 private $dataCol; 48 49 /** 50 * @var string Column for timestamp 51 */ 52 private $timeCol; 53 54 /** 55 * Constructor. 56 * 57 * List of available options: 58 * * db_table: The name of the table [required] 59 * * db_id_col: The column where to store the session id [default: sess_id] 60 * * db_data_col: The column where to store the session data [default: sess_data] 61 * * db_time_col: The column where to store the timestamp [default: sess_time] 62 * 63 * @param \PDO $pdo A \PDO instance 64 * @param array $dbOptions An associative array of DB options 65 * 66 * @throws \InvalidArgumentException When "db_table" option is not provided 67 */ 68 public function __construct(\PDO $pdo, array $dbOptions = array()) 69 { 70 if (!array_key_exists('db_table', $dbOptions)) { 71 throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.'); 72 } 73 if (\PDO::ERRMODE_EXCEPTION !== $pdo->getAttribute(\PDO::ATTR_ERRMODE)) { 74 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__)); 75 } 76 $this->pdo = $pdo; 77 $dbOptions = array_merge(array( 78 'db_id_col' => 'sess_id', 79 'db_data_col' => 'sess_data', 80 'db_time_col' => 'sess_time', 81 ), $dbOptions); 82 83 $this->table = $dbOptions['db_table']; 84 $this->idCol = $dbOptions['db_id_col']; 85 $this->dataCol = $dbOptions['db_data_col']; 86 $this->timeCol = $dbOptions['db_time_col']; 87 } 88 89 /** 90 * {@inheritdoc} 91 */ 92 public function open($savePath, $sessionName) 93 { 94 return true; 95 } 96 97 /** 98 * {@inheritdoc} 99 */ 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Jan 11 00:25:41 2018 | Cross-referenced by PHPXref 0.7.1 |