MongoDbSessionHandler.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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. * Session handler using the mongodb/mongodb package and MongoDB driver extension.
  13. *
  14. * @author Markus Bachmann <markus.bachmann@bachi.biz>
  15. *
  16. * @see https://packagist.org/packages/mongodb/mongodb
  17. * @see http://php.net/manual/en/set.mongodb.php
  18. */
  19. class MongoDbSessionHandler extends AbstractSessionHandler
  20. {
  21. private $mongo;
  22. /**
  23. * @var \MongoCollection
  24. */
  25. private $collection;
  26. /**
  27. * @var array
  28. */
  29. private $options;
  30. /**
  31. * Constructor.
  32. *
  33. * List of available options:
  34. * * database: The name of the database [required]
  35. * * collection: The name of the collection [required]
  36. * * id_field: The field name for storing the session id [default: _id]
  37. * * data_field: The field name for storing the session data [default: data]
  38. * * time_field: The field name for storing the timestamp [default: time]
  39. * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
  40. *
  41. * It is strongly recommended to put an index on the `expiry_field` for
  42. * garbage-collection. Alternatively it's possible to automatically expire
  43. * the sessions in the database as described below:
  44. *
  45. * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions
  46. * automatically. Such an index can for example look like this:
  47. *
  48. * db.<session-collection>.ensureIndex(
  49. * { "<expiry-field>": 1 },
  50. * { "expireAfterSeconds": 0 }
  51. * )
  52. *
  53. * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/
  54. *
  55. * If you use such an index, you can drop `gc_probability` to 0 since
  56. * no garbage-collection is required.
  57. *
  58. * @param \MongoDB\Client $mongo A MongoDB\Client instance
  59. * @param array $options An associative array of field options
  60. *
  61. * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
  62. * @throws \InvalidArgumentException When "database" or "collection" not provided
  63. */
  64. public function __construct($mongo, array $options)
  65. {
  66. if ($mongo instanceof \MongoClient || $mongo instanceof \Mongo) {
  67. @trigger_error(sprintf('Using %s with the legacy mongo extension is deprecated as of 3.4 and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.', __CLASS__), E_USER_DEPRECATED);
  68. }
  69. if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
  70. throw new \InvalidArgumentException('MongoClient or Mongo instance required');
  71. }
  72. if (!isset($options['database']) || !isset($options['collection'])) {
  73. throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler');
  74. }
  75. $this->mongo = $mongo;
  76. $this->options = array_merge([
  77. 'id_field' => '_id',
  78. 'data_field' => 'data',
  79. 'time_field' => 'time',
  80. 'expiry_field' => 'expires_at',
  81. ], $options);
  82. }
  83. /**
  84. * {@inheritdoc}
  85. */
  86. public function close()
  87. {
  88. return true;
  89. }
  90. /**
  91. * {@inheritdoc}
  92. */
  93. protected function doDestroy($sessionId)
  94. {
  95. $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
  96. $this->getCollection()->$methodName([
  97. $this->options['id_field'] => $sessionId,
  98. ]);
  99. return true;
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function gc($maxlifetime)
  105. {
  106. $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove';
  107. $this->getCollection()->$methodName([
  108. $this->options['expiry_field'] => ['$lt' => $this->createDateTime()],
  109. ]);
  110. return true;
  111. }
  112. /**
  113. * {@inheritdoc}
  114. */
  115. protected function doWrite($sessionId, $data)
  116. {
  117. $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
  118. $fields = [
  119. $this->options['time_field'] => $this->createDateTime(),
  120. $this->options['expiry_field'] => $expiry,
  121. ];
  122. $options = ['upsert' => true];
  123. if ($this->mongo instanceof \MongoDB\Client) {
  124. $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
  125. } else {
  126. $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY);
  127. $options['multiple'] = false;
  128. }
  129. $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update';
  130. $this->getCollection()->$methodName(
  131. [$this->options['id_field'] => $sessionId],
  132. ['$set' => $fields],
  133. $options
  134. );
  135. return true;
  136. }
  137. /**
  138. * {@inheritdoc}
  139. */
  140. public function updateTimestamp($sessionId, $data)
  141. {
  142. $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
  143. if ($this->mongo instanceof \MongoDB\Client) {
  144. $methodName = 'updateOne';
  145. $options = [];
  146. } else {
  147. $methodName = 'update';
  148. $options = ['multiple' => false];
  149. }
  150. $this->getCollection()->$methodName(
  151. [$this->options['id_field'] => $sessionId],
  152. ['$set' => [
  153. $this->options['time_field'] => $this->createDateTime(),
  154. $this->options['expiry_field'] => $expiry,
  155. ]],
  156. $options
  157. );
  158. return true;
  159. }
  160. /**
  161. * {@inheritdoc}
  162. */
  163. protected function doRead($sessionId)
  164. {
  165. $dbData = $this->getCollection()->findOne([
  166. $this->options['id_field'] => $sessionId,
  167. $this->options['expiry_field'] => ['$gte' => $this->createDateTime()],
  168. ]);
  169. if (null === $dbData) {
  170. return '';
  171. }
  172. if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) {
  173. return $dbData[$this->options['data_field']]->getData();
  174. }
  175. return $dbData[$this->options['data_field']]->bin;
  176. }
  177. /**
  178. * Return a "MongoCollection" instance.
  179. *
  180. * @return \MongoCollection
  181. */
  182. private function getCollection()
  183. {
  184. if (null === $this->collection) {
  185. $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
  186. }
  187. return $this->collection;
  188. }
  189. /**
  190. * Return a Mongo instance.
  191. *
  192. * @return \Mongo|\MongoClient|\MongoDB\Client
  193. */
  194. protected function getMongo()
  195. {
  196. return $this->mongo;
  197. }
  198. /**
  199. * Create a date object using the class appropriate for the current mongo connection.
  200. *
  201. * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
  202. *
  203. * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now.
  204. *
  205. * @return \MongoDate|\MongoDB\BSON\UTCDateTime
  206. */
  207. private function createDateTime($seconds = null)
  208. {
  209. if (null === $seconds) {
  210. $seconds = time();
  211. }
  212. if ($this->mongo instanceof \MongoDB\Client) {
  213. return new \MongoDB\BSON\UTCDateTime($seconds * 1000);
  214. }
  215. return new \MongoDate($seconds);
  216. }
  217. }