Session.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\mongodb;
  8. use Yii;
  9. use yii\base\ErrorHandler;
  10. use yii\base\InvalidConfigException;
  11. use yii\di\Instance;
  12. use yii\web\MultiFieldSession;
  13. /**
  14. * Session extends [[\yii\web\Session]] by using MongoDB as session data storage.
  15. *
  16. * By default, Session stores session data in a collection named 'session' inside the default database.
  17. * This collection is better to be pre-created with fields 'id' and 'expire' indexed.
  18. * The collection name can be changed by setting [[sessionCollection]].
  19. *
  20. * The following example shows how you can configure the application to use Session:
  21. * Add the following to your application config under `components`:
  22. *
  23. * ```php
  24. * 'session' => [
  25. * 'class' => 'yii\mongodb\Session',
  26. * // 'db' => 'mymongodb',
  27. * // 'sessionCollection' => 'my_session',
  28. * ]
  29. * ```
  30. *
  31. * Session extends [[MultiFieldSession]], thus it allows saving extra fields into the [[sessionCollection]].
  32. * Refer to [[MultiFieldSession]] for more details.
  33. *
  34. * Tip: you can use MongoDB [TTL index](https://docs.mongodb.com/manual/tutorial/expire-data/) for the session garbage
  35. * collection for performance saving, in this case you should set [[Session::gCProbability]] to `0`.
  36. *
  37. * @author Paul Klimov <klimov.paul@gmail.com>
  38. * @since 2.0
  39. */
  40. class Session extends MultiFieldSession
  41. {
  42. /**
  43. * @var Connection|array|string the MongoDB connection object or the application component ID of the MongoDB connection.
  44. * After the Session object is created, if you want to change this property, you should only assign it
  45. * with a MongoDB connection object.
  46. * Starting from version 2.0.2, this can also be a configuration array for creating the object.
  47. */
  48. public $db = 'mongodb';
  49. /**
  50. * @var string|array the name of the MongoDB collection that stores the session data.
  51. * Please refer to [[Connection::getCollection()]] on how to specify this parameter.
  52. * This collection is better to be pre-created with fields 'id' and 'expire' indexed.
  53. */
  54. public $sessionCollection = 'session';
  55. /**
  56. * @var array Session fields to be written into session table columns
  57. * @since 2.1.8
  58. */
  59. protected $fields = [];
  60. /**
  61. * Initializes the Session component.
  62. * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection.
  63. * @throws InvalidConfigException if [[db]] is invalid.
  64. */
  65. public function init()
  66. {
  67. parent::init();
  68. $this->db = Instance::ensure($this->db, Connection::className());
  69. }
  70. /**
  71. * Session open handler.
  72. * @internal Do not call this method directly.
  73. * @param string $savePath session save path
  74. * @param string $sessionName session name
  75. * @return bool whether session is opened successfully
  76. */
  77. public function openSession($savePath, $sessionName)
  78. {
  79. if ($this->getUseStrictMode()) {
  80. $id = $this->getId();
  81. $collection = $this->db->getCollection($this->sessionCollection);
  82. $condition = [
  83. 'id' => $id,
  84. 'expire' => ['$gt' => time()],
  85. ];
  86. if (!$collection->documentExists($condition)) {
  87. //This session id does not exist, mark it for forced regeneration
  88. $this->_forceRegenerateId = $id;
  89. }
  90. }
  91. return parent::openSession($savePath, $sessionName);
  92. }
  93. /**
  94. * Updates the current session ID with a newly generated one.
  95. * Please refer to <http://php.net/session_regenerate_id> for more details.
  96. * @param bool $deleteOldSession Whether to delete the old associated session file or not.
  97. */
  98. public function regenerateID($deleteOldSession = false)
  99. {
  100. $oldID = session_id();
  101. // if no session is started, there is nothing to regenerate
  102. if (empty($oldID)) {
  103. return;
  104. }
  105. parent::regenerateID(false);
  106. $newID = session_id();
  107. $collection = $this->db->getCollection($this->sessionCollection);
  108. $row = $collection->findOne(['id' => $oldID]);
  109. if ($row !== null) {
  110. if ($deleteOldSession) {
  111. $collection->update(['id' => $oldID], ['id' => $newID]);
  112. } else {
  113. unset($row['_id']);
  114. $row['id'] = $newID;
  115. $collection->insert($row);
  116. }
  117. } else {
  118. // shouldn't reach here normally
  119. $collection->insert($this->composeFields($newID, ''));
  120. }
  121. }
  122. /**
  123. * Session read handler.
  124. * Do not call this method directly.
  125. * @param string $id session ID
  126. * @return string the session data
  127. */
  128. public function readSession($id)
  129. {
  130. $collection = $this->db->getCollection($this->sessionCollection);
  131. $condition = [
  132. 'id' => $id,
  133. 'expire' => ['$gt' => time()],
  134. ];
  135. if (isset($this->readCallback)) {
  136. $doc = $collection->findOne($condition);
  137. return $doc === null ? '' : $this->extractData($doc);
  138. }
  139. $doc = $collection->findOne(
  140. $condition,
  141. ['data' => 1, '_id' => 0]
  142. );
  143. return isset($doc['data']) ? $doc['data'] : '';
  144. }
  145. /**
  146. * Session write handler.
  147. * Do not call this method directly.
  148. * @param string $id session ID
  149. * @param string $data session data
  150. * @return bool whether session write is successful
  151. */
  152. public function writeSession($id, $data)
  153. {
  154. if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) {
  155. //Ignore write when forceRegenerate is active for this id
  156. return true;
  157. }
  158. // exception must be caught in session write handler
  159. // http://us.php.net/manual/en/function.session-set-save-handler.php
  160. try {
  161. // ensure backwards compatability, related to:
  162. // https://github.com/yiisoft/yii2/pull/17188
  163. // https://github.com/yiisoft/yii2/pull/17559
  164. if ($this->writeCallback && !$this->fields) {
  165. $this->fields = $this->composeFields();
  166. }
  167. // ensure data consistency
  168. if (!isset($this->fields['data'])) {
  169. $this->fields['data'] = $data;
  170. } else {
  171. $_SESSION = $this->fields['data'];
  172. }
  173. // ensure 'id' and 'expire' are never affected by [[writeCallback]]
  174. $this->fields = array_merge($this->fields, [
  175. 'id' => $id,
  176. 'expire' => time() + $this->getTimeout(),
  177. ]);
  178. $this->db->getCollection($this->sessionCollection)->update(
  179. ['id' => $id],
  180. $this->fields,
  181. ['upsert' => true]
  182. );
  183. $this->fields = [];
  184. } catch (\Exception $e) {
  185. Yii::$app->errorHandler->handleException($e);
  186. return false;
  187. }
  188. return true;
  189. }
  190. /**
  191. * Session destroy handler.
  192. * Do not call this method directly.
  193. * @param string $id session ID
  194. * @return bool whether session is destroyed successfully
  195. */
  196. public function destroySession($id)
  197. {
  198. $this->db->getCollection($this->sessionCollection)->remove(
  199. ['id' => $id],
  200. ['justOne' => true]
  201. );
  202. return true;
  203. }
  204. /**
  205. * Session GC (garbage collection) handler.
  206. * Do not call this method directly.
  207. * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
  208. * @return bool whether session is GCed successfully
  209. */
  210. public function gcSession($maxLifetime)
  211. {
  212. $this->db->getCollection($this->sessionCollection)
  213. ->remove(['expire' => ['$lt' => time()]]);
  214. return true;
  215. }
  216. }