PageRenderTime 209ms CodeModel.GetById 24ms RepoModel.GetById 7ms app.codeStats 0ms

/vendor/symfony/symfony/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php

https://gitlab.com/Isaki/le331.fr
PHP | 246 lines | 134 code | 33 blank | 79 comment | 10 complexity | 20725104ee4ad33b45fa26dfee9c6616 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\Doctrine\HttpFoundation;
  11. use Doctrine\DBAL\Connection;
  12. use Doctrine\DBAL\Driver\DriverException;
  13. use Doctrine\DBAL\Platforms\SQLServer2008Platform;
  14. /**
  15. * DBAL based session storage.
  16. *
  17. * This implementation is very similar to Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
  18. * but uses a Doctrine connection and thus also works with non-PDO-based drivers like mysqli and OCI8.
  19. *
  20. * @author Fabien Potencier <fabien@symfony.com>
  21. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  22. * @author Tobias Schultze <http://tobion.de>
  23. */
  24. class DbalSessionHandler implements \SessionHandlerInterface
  25. {
  26. /**
  27. * @var Connection
  28. */
  29. private $con;
  30. /**
  31. * @var string
  32. */
  33. private $table;
  34. /**
  35. * @var string Column for session id
  36. */
  37. private $idCol = 'sess_id';
  38. /**
  39. * @var string Column for session data
  40. */
  41. private $dataCol = 'sess_data';
  42. /**
  43. * @var string Column for timestamp
  44. */
  45. private $timeCol = 'sess_time';
  46. /**
  47. * Constructor.
  48. *
  49. * @param Connection $con A connection
  50. * @param string $tableName Table name
  51. */
  52. public function __construct(Connection $con, $tableName = 'sessions')
  53. {
  54. $this->con = $con;
  55. $this->table = $tableName;
  56. }
  57. /**
  58. * {@inheritdoc}
  59. */
  60. public function open($savePath, $sessionName)
  61. {
  62. return true;
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. public function close()
  68. {
  69. return true;
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function destroy($sessionId)
  75. {
  76. // delete the record associated with this id
  77. $sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
  78. try {
  79. $stmt = $this->con->prepare($sql);
  80. $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
  81. $stmt->execute();
  82. } catch (\Exception $e) {
  83. throw new \RuntimeException(sprintf('Exception was thrown when trying to delete a session: %s', $e->getMessage()), 0, $e);
  84. }
  85. return true;
  86. }
  87. /**
  88. * {@inheritdoc}
  89. */
  90. public function gc($maxlifetime)
  91. {
  92. // delete the session records that have expired
  93. $sql = "DELETE FROM $this->table WHERE $this->timeCol < :time";
  94. try {
  95. $stmt = $this->con->prepare($sql);
  96. $stmt->bindValue(':time', time() - $maxlifetime, \PDO::PARAM_INT);
  97. $stmt->execute();
  98. } catch (\Exception $e) {
  99. throw new \RuntimeException(sprintf('Exception was thrown when trying to delete expired sessions: %s', $e->getMessage()), 0, $e);
  100. }
  101. return true;
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public function read($sessionId)
  107. {
  108. $sql = "SELECT $this->dataCol FROM $this->table WHERE $this->idCol = :id";
  109. try {
  110. $stmt = $this->con->prepare($sql);
  111. $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
  112. $stmt->execute();
  113. // We use fetchAll instead of fetchColumn to make sure the DB cursor gets closed
  114. $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
  115. if ($sessionRows) {
  116. return base64_decode($sessionRows[0][0]);
  117. }
  118. return '';
  119. } catch (\Exception $e) {
  120. throw new \RuntimeException(sprintf('Exception was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
  121. }
  122. }
  123. /**
  124. * {@inheritdoc}
  125. */
  126. public function write($sessionId, $data)
  127. {
  128. $encoded = base64_encode($data);
  129. try {
  130. // We use a single MERGE SQL query when supported by the database.
  131. $mergeSql = $this->getMergeSql();
  132. if (null !== $mergeSql) {
  133. $mergeStmt = $this->con->prepare($mergeSql);
  134. $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
  135. $mergeStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
  136. $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
  137. // Oracle has a bug that will intermittently happen if you
  138. // have only 1 bind on a CLOB field for 2 different statements
  139. // (INSERT and UPDATE in this case)
  140. if ('oracle' == $this->con->getDatabasePlatform()->getName()) {
  141. $mergeStmt->bindParam(':data2', $encoded, \PDO::PARAM_STR);
  142. }
  143. $mergeStmt->execute();
  144. return true;
  145. }
  146. $updateStmt = $this->con->prepare(
  147. "UPDATE $this->table SET $this->dataCol = :data, $this->timeCol = :time WHERE $this->idCol = :id"
  148. );
  149. $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
  150. $updateStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
  151. $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
  152. $updateStmt->execute();
  153. // When MERGE is not supported, like in Postgres, we have to use this approach that can result in
  154. // duplicate key errors when the same session is written simultaneously. We can just catch such an
  155. // error and re-execute the update. This is similar to a serializable transaction with retry logic
  156. // on serialization failures but without the overhead and without possible false positives due to
  157. // longer gap locking.
  158. if (!$updateStmt->rowCount()) {
  159. try {
  160. $insertStmt = $this->con->prepare(
  161. "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"
  162. );
  163. $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
  164. $insertStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
  165. $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
  166. $insertStmt->execute();
  167. } catch (\Exception $e) {
  168. $driverException = $e->getPrevious();
  169. // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
  170. // DriverException only available since DBAL 2.5
  171. if (
  172. ($driverException instanceof DriverException && 0 === strpos($driverException->getSQLState(), '23')) ||
  173. ($driverException instanceof \PDOException && 0 === strpos($driverException->getCode(), '23'))
  174. ) {
  175. $updateStmt->execute();
  176. } else {
  177. throw $e;
  178. }
  179. }
  180. }
  181. } catch (\Exception $e) {
  182. throw new \RuntimeException(sprintf('Exception was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
  183. }
  184. return true;
  185. }
  186. /**
  187. * Returns a merge/upsert (i.e. insert or update) SQL query when supported by the database.
  188. *
  189. * @return string|null The SQL string or null when not supported
  190. */
  191. private function getMergeSql()
  192. {
  193. $platform = $this->con->getDatabasePlatform()->getName();
  194. switch ($platform) {
  195. case 'mysql':
  196. return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
  197. "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)";
  198. case 'oracle':
  199. // DUAL is Oracle specific dummy table
  200. return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ".
  201. "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
  202. "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data2, $this->timeCol = :time";
  203. case $this->con->getDatabasePlatform() instanceof SQLServer2008Platform:
  204. // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
  205. // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
  206. return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ".
  207. "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ".
  208. "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;";
  209. case 'sqlite':
  210. return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)";
  211. }
  212. }
  213. }