PdoSessionHandler.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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\Component\HttpFoundation\Session\Storage\Handler;
  11. /**
  12. * PdoSessionHandler.
  13. *
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. * @author Michael Williams <michael.williams@funsational.com>
  16. */
  17. class PdoSessionHandler implements \SessionHandlerInterface
  18. {
  19. /**
  20. * PDO instance.
  21. *
  22. * @var \PDO
  23. */
  24. private $pdo;
  25. /**
  26. * Database options.
  27. *
  28. *
  29. * @var array
  30. */
  31. private $dbOptions;
  32. /**
  33. * Constructor.
  34. *
  35. * @param \PDO $pdo A \PDO instance
  36. * @param array $dbOptions An associative array of DB options
  37. * @param array $options Session configuration options
  38. *
  39. * @throws \InvalidArgumentException When "db_table" option is not provided
  40. */
  41. public function __construct(\PDO $pdo, array $dbOptions = array(), array $options = array())
  42. {
  43. if (!array_key_exists('db_table', $dbOptions)) {
  44. throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.');
  45. }
  46. $this->pdo = $pdo;
  47. $this->dbOptions = array_merge(array(
  48. 'db_id_col' => 'sess_id',
  49. 'db_data_col' => 'sess_data',
  50. 'db_time_col' => 'sess_time',
  51. ), $dbOptions);
  52. }
  53. /**
  54. * {@inheritdoc}
  55. */
  56. public function open($path, $name)
  57. {
  58. return true;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. */
  63. public function close()
  64. {
  65. return true;
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function destroy($id)
  71. {
  72. // get table/column
  73. $dbTable = $this->dbOptions['db_table'];
  74. $dbIdCol = $this->dbOptions['db_id_col'];
  75. // delete the record associated with this id
  76. $sql = "DELETE FROM $dbTable WHERE $dbIdCol = :id";
  77. try {
  78. $stmt = $this->pdo->prepare($sql);
  79. $stmt->bindParam(':id', $id, \PDO::PARAM_STR);
  80. $stmt->execute();
  81. } catch (\PDOException $e) {
  82. throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
  83. }
  84. return true;
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. public function gc($lifetime)
  90. {
  91. // get table/column
  92. $dbTable = $this->dbOptions['db_table'];
  93. $dbTimeCol = $this->dbOptions['db_time_col'];
  94. // delete the session records that have expired
  95. $sql = "DELETE FROM $dbTable WHERE $dbTimeCol < (:time - $lifetime)";
  96. try {
  97. $stmt = $this->pdo->prepare($sql);
  98. $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
  99. $stmt->execute();
  100. } catch (\PDOException $e) {
  101. throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
  102. }
  103. return true;
  104. }
  105. /**
  106. * {@inheritdoc}
  107. */
  108. public function read($id)
  109. {
  110. // get table/columns
  111. $dbTable = $this->dbOptions['db_table'];
  112. $dbDataCol = $this->dbOptions['db_data_col'];
  113. $dbIdCol = $this->dbOptions['db_id_col'];
  114. try {
  115. $sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id";
  116. $stmt = $this->pdo->prepare($sql);
  117. $stmt->bindParam(':id', $id, \PDO::PARAM_STR);
  118. $stmt->execute();
  119. // it is recommended to use fetchAll so that PDO can close the DB cursor
  120. // we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777
  121. $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
  122. if (count($sessionRows) == 1) {
  123. return base64_decode($sessionRows[0][0]);
  124. }
  125. // session does not exist, create it
  126. $this->createNewSession($id);
  127. return '';
  128. } catch (\PDOException $e) {
  129. throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
  130. }
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function write($id, $data)
  136. {
  137. // get table/column
  138. $dbTable = $this->dbOptions['db_table'];
  139. $dbDataCol = $this->dbOptions['db_data_col'];
  140. $dbIdCol = $this->dbOptions['db_id_col'];
  141. $dbTimeCol = $this->dbOptions['db_time_col'];
  142. $sql = ('mysql' === $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME))
  143. ? "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time) "
  144. ."ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = CASE WHEN $dbTimeCol = :time THEN (VALUES($dbTimeCol) + 1) ELSE VALUES($dbTimeCol) END"
  145. : "UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id";
  146. try {
  147. //session data can contain non binary safe characters so we need to encode it
  148. $encoded = base64_encode($data);
  149. $stmt = $this->pdo->prepare($sql);
  150. $stmt->bindParam(':id', $id, \PDO::PARAM_STR);
  151. $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
  152. $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
  153. $stmt->execute();
  154. if (!$stmt->rowCount()) {
  155. // No session exists in the database to update. This happens when we have called
  156. // session_regenerate_id()
  157. $this->createNewSession($id, $data);
  158. }
  159. } catch (\PDOException $e) {
  160. throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
  161. }
  162. return true;
  163. }
  164. /**
  165. * Creates a new session with the given $id and $data
  166. *
  167. * @param string $id
  168. * @param string $data
  169. *
  170. * @return boolean True.
  171. */
  172. private function createNewSession($id, $data = '')
  173. {
  174. // get table/column
  175. $dbTable = $this->dbOptions['db_table'];
  176. $dbDataCol = $this->dbOptions['db_data_col'];
  177. $dbIdCol = $this->dbOptions['db_id_col'];
  178. $dbTimeCol = $this->dbOptions['db_time_col'];
  179. $sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time)";
  180. //session data can contain non binary safe characters so we need to encode it
  181. $encoded = base64_encode($data);
  182. $stmt = $this->pdo->prepare($sql);
  183. $stmt->bindParam(':id', $id, \PDO::PARAM_STR);
  184. $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
  185. $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
  186. $stmt->execute();
  187. return true;
  188. }
  189. }