Session.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  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\web;
  8. use Yii;
  9. use yii\base\Component;
  10. use yii\base\InvalidArgumentException;
  11. use yii\base\InvalidConfigException;
  12. /**
  13. * Session provides session data management and the related configurations.
  14. *
  15. * Session is a Web application component that can be accessed via `Yii::$app->session`.
  16. *
  17. * To start the session, call [[open()]]; To complete and send out session data, call [[close()]];
  18. * To destroy the session, call [[destroy()]].
  19. *
  20. * Session can be used like an array to set and get session data. For example,
  21. *
  22. * ```php
  23. * $session = new Session;
  24. * $session->open();
  25. * $value1 = $session['name1']; // get session variable 'name1'
  26. * $value2 = $session['name2']; // get session variable 'name2'
  27. * foreach ($session as $name => $value) // traverse all session variables
  28. * $session['name3'] = $value3; // set session variable 'name3'
  29. * ```
  30. *
  31. * Session can be extended to support customized session storage.
  32. * To do so, override [[useCustomStorage]] so that it returns true, and
  33. * override these methods with the actual logic about using custom storage:
  34. * [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
  35. * [[destroySession()]] and [[gcSession()]].
  36. *
  37. * Session also supports a special type of session data, called *flash messages*.
  38. * A flash message is available only in the current request and the next request.
  39. * After that, it will be deleted automatically. Flash messages are particularly
  40. * useful for displaying confirmation messages. To use flash messages, simply
  41. * call methods such as [[setFlash()]], [[getFlash()]].
  42. *
  43. * For more details and usage information on Session, see the [guide article on sessions](guide:runtime-sessions-cookies).
  44. *
  45. * @property array $allFlashes Flash messages (key => message or key => [message1, message2]). This property
  46. * is read-only.
  47. * @property string $cacheLimiter Current cache limiter. This property is read-only.
  48. * @property array $cookieParams The session cookie parameters. This property is read-only.
  49. * @property int $count The number of session variables. This property is read-only.
  50. * @property string $flash The key identifying the flash message. Note that flash messages and normal session
  51. * variables share the same name space. If you have a normal session variable using the same name, its value will
  52. * be overwritten by this method. This property is write-only.
  53. * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is
  54. * started on every session initialization, defaults to 1 meaning 1% chance.
  55. * @property bool $hasSessionId Whether the current request has sent the session ID.
  56. * @property string $id The current session ID.
  57. * @property bool $isActive Whether the session has started. This property is read-only.
  58. * @property SessionIterator $iterator An iterator for traversing the session variables. This property is
  59. * read-only.
  60. * @property string $name The current session name.
  61. * @property string $savePath The current session save path, defaults to '/tmp'.
  62. * @property int $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. The
  63. * default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
  64. * @property bool|null $useCookies The value indicating whether cookies should be used to store session IDs.
  65. * @property bool $useCustomStorage Whether to use custom storage. This property is read-only.
  66. * @property bool $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
  67. * false.
  68. *
  69. * @author Qiang Xue <qiang.xue@gmail.com>
  70. * @since 2.0
  71. */
  72. class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
  73. {
  74. /**
  75. * @var string the name of the session variable that stores the flash message data.
  76. */
  77. public $flashParam = '__flash';
  78. /**
  79. * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
  80. */
  81. public $handler;
  82. /**
  83. * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
  84. * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly'
  85. * @see http://www.php.net/manual/en/function.session-set-cookie-params.php
  86. */
  87. private $_cookieParams = ['httponly' => true];
  88. /**
  89. * @var $frozenSessionData array|null is used for saving session between recreations due to session parameters update.
  90. */
  91. private $frozenSessionData;
  92. /**
  93. * Initializes the application component.
  94. * This method is required by IApplicationComponent and is invoked by application.
  95. */
  96. public function init()
  97. {
  98. parent::init();
  99. register_shutdown_function([$this, 'close']);
  100. if ($this->getIsActive()) {
  101. Yii::warning('Session is already started', __METHOD__);
  102. $this->updateFlashCounters();
  103. }
  104. }
  105. /**
  106. * Returns a value indicating whether to use custom session storage.
  107. * This method should be overridden to return true by child classes that implement custom session storage.
  108. * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
  109. * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
  110. * @return bool whether to use custom storage.
  111. */
  112. public function getUseCustomStorage()
  113. {
  114. return false;
  115. }
  116. /**
  117. * Starts the session.
  118. */
  119. public function open()
  120. {
  121. if ($this->getIsActive()) {
  122. return;
  123. }
  124. $this->registerSessionHandler();
  125. $this->setCookieParamsInternal();
  126. YII_DEBUG ? session_start() : @session_start();
  127. if ($this->getIsActive()) {
  128. Yii::info('Session started', __METHOD__);
  129. $this->updateFlashCounters();
  130. } else {
  131. $error = error_get_last();
  132. $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
  133. Yii::error($message, __METHOD__);
  134. }
  135. }
  136. /**
  137. * Registers session handler.
  138. * @throws \yii\base\InvalidConfigException
  139. */
  140. protected function registerSessionHandler()
  141. {
  142. if ($this->handler !== null) {
  143. if (!is_object($this->handler)) {
  144. $this->handler = Yii::createObject($this->handler);
  145. }
  146. if (!$this->handler instanceof \SessionHandlerInterface) {
  147. throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
  148. }
  149. YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
  150. } elseif ($this->getUseCustomStorage()) {
  151. if (YII_DEBUG) {
  152. session_set_save_handler(
  153. [$this, 'openSession'],
  154. [$this, 'closeSession'],
  155. [$this, 'readSession'],
  156. [$this, 'writeSession'],
  157. [$this, 'destroySession'],
  158. [$this, 'gcSession']
  159. );
  160. } else {
  161. @session_set_save_handler(
  162. [$this, 'openSession'],
  163. [$this, 'closeSession'],
  164. [$this, 'readSession'],
  165. [$this, 'writeSession'],
  166. [$this, 'destroySession'],
  167. [$this, 'gcSession']
  168. );
  169. }
  170. }
  171. }
  172. /**
  173. * Ends the current session and store session data.
  174. */
  175. public function close()
  176. {
  177. if ($this->getIsActive()) {
  178. YII_DEBUG ? session_write_close() : @session_write_close();
  179. }
  180. }
  181. /**
  182. * Frees all session variables and destroys all data registered to a session.
  183. *
  184. * This method has no effect when session is not [[getIsActive()|active]].
  185. * Make sure to call [[open()]] before calling it.
  186. * @see open()
  187. * @see isActive
  188. */
  189. public function destroy()
  190. {
  191. if ($this->getIsActive()) {
  192. $sessionId = session_id();
  193. $this->close();
  194. $this->setId($sessionId);
  195. $this->open();
  196. session_unset();
  197. session_destroy();
  198. $this->setId($sessionId);
  199. }
  200. }
  201. /**
  202. * @return bool whether the session has started
  203. */
  204. public function getIsActive()
  205. {
  206. return session_status() === PHP_SESSION_ACTIVE;
  207. }
  208. private $_hasSessionId;
  209. /**
  210. * Returns a value indicating whether the current request has sent the session ID.
  211. * The default implementation will check cookie and $_GET using the session name.
  212. * If you send session ID via other ways, you may need to override this method
  213. * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
  214. * @return bool whether the current request has sent the session ID.
  215. */
  216. public function getHasSessionId()
  217. {
  218. if ($this->_hasSessionId === null) {
  219. $name = $this->getName();
  220. $request = Yii::$app->getRequest();
  221. if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
  222. $this->_hasSessionId = true;
  223. } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
  224. $this->_hasSessionId = $request->get($name) != '';
  225. } else {
  226. $this->_hasSessionId = false;
  227. }
  228. }
  229. return $this->_hasSessionId;
  230. }
  231. /**
  232. * Sets the value indicating whether the current request has sent the session ID.
  233. * This method is provided so that you can override the default way of determining
  234. * whether the session ID is sent.
  235. * @param bool $value whether the current request has sent the session ID.
  236. */
  237. public function setHasSessionId($value)
  238. {
  239. $this->_hasSessionId = $value;
  240. }
  241. /**
  242. * Gets the session ID.
  243. * This is a wrapper for [PHP session_id()](http://php.net/manual/en/function.session-id.php).
  244. * @return string the current session ID
  245. */
  246. public function getId()
  247. {
  248. return session_id();
  249. }
  250. /**
  251. * Sets the session ID.
  252. * This is a wrapper for [PHP session_id()](http://php.net/manual/en/function.session-id.php).
  253. * @param string $value the session ID for the current session
  254. */
  255. public function setId($value)
  256. {
  257. session_id($value);
  258. }
  259. /**
  260. * Updates the current session ID with a newly generated one.
  261. *
  262. * Please refer to <http://php.net/session_regenerate_id> for more details.
  263. *
  264. * This method has no effect when session is not [[getIsActive()|active]].
  265. * Make sure to call [[open()]] before calling it.
  266. *
  267. * @param bool $deleteOldSession Whether to delete the old associated session file or not.
  268. * @see open()
  269. * @see isActive
  270. */
  271. public function regenerateID($deleteOldSession = false)
  272. {
  273. if ($this->getIsActive()) {
  274. // add @ to inhibit possible warning due to race condition
  275. // https://github.com/yiisoft/yii2/pull/1812
  276. if (YII_DEBUG && !headers_sent()) {
  277. session_regenerate_id($deleteOldSession);
  278. } else {
  279. @session_regenerate_id($deleteOldSession);
  280. }
  281. }
  282. }
  283. /**
  284. * Gets the name of the current session.
  285. * This is a wrapper for [PHP session_name()](http://php.net/manual/en/function.session-name.php).
  286. * @return string the current session name
  287. */
  288. public function getName()
  289. {
  290. return session_name();
  291. }
  292. /**
  293. * Sets the name for the current session.
  294. * This is a wrapper for [PHP session_name()](http://php.net/manual/en/function.session-name.php).
  295. * @param string $value the session name for the current session, must be an alphanumeric string.
  296. * It defaults to "PHPSESSID".
  297. */
  298. public function setName($value)
  299. {
  300. $this->freeze();
  301. session_name($value);
  302. $this->unfreeze();
  303. }
  304. /**
  305. * Gets the current session save path.
  306. * This is a wrapper for [PHP session_save_path()](http://php.net/manual/en/function.session-save-path.php).
  307. * @return string the current session save path, defaults to '/tmp'.
  308. */
  309. public function getSavePath()
  310. {
  311. return session_save_path();
  312. }
  313. /**
  314. * Sets the current session save path.
  315. * This is a wrapper for [PHP session_save_path()](http://php.net/manual/en/function.session-save-path.php).
  316. * @param string $value the current session save path. This can be either a directory name or a [path alias](guide:concept-aliases).
  317. * @throws InvalidArgumentException if the path is not a valid directory
  318. */
  319. public function setSavePath($value)
  320. {
  321. $path = Yii::getAlias($value);
  322. if (is_dir($path)) {
  323. session_save_path($path);
  324. } else {
  325. throw new InvalidArgumentException("Session save path is not a valid directory: $value");
  326. }
  327. }
  328. /**
  329. * @return array the session cookie parameters.
  330. * @see http://php.net/manual/en/function.session-get-cookie-params.php
  331. */
  332. public function getCookieParams()
  333. {
  334. return array_merge(session_get_cookie_params(), array_change_key_case($this->_cookieParams));
  335. }
  336. /**
  337. * Sets the session cookie parameters.
  338. * The cookie parameters passed to this method will be merged with the result
  339. * of `session_get_cookie_params()`.
  340. * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`.
  341. * @throws InvalidArgumentException if the parameters are incomplete.
  342. * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
  343. */
  344. public function setCookieParams(array $value)
  345. {
  346. $this->_cookieParams = $value;
  347. }
  348. /**
  349. * Sets the session cookie parameters.
  350. * This method is called by [[open()]] when it is about to open the session.
  351. * @throws InvalidArgumentException if the parameters are incomplete.
  352. * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
  353. */
  354. private function setCookieParamsInternal()
  355. {
  356. $data = $this->getCookieParams();
  357. if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) {
  358. session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
  359. } else {
  360. throw new InvalidArgumentException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.');
  361. }
  362. }
  363. /**
  364. * Returns the value indicating whether cookies should be used to store session IDs.
  365. * @return bool|null the value indicating whether cookies should be used to store session IDs.
  366. * @see setUseCookies()
  367. */
  368. public function getUseCookies()
  369. {
  370. if (ini_get('session.use_cookies') === '0') {
  371. return false;
  372. } elseif (ini_get('session.use_only_cookies') === '1') {
  373. return true;
  374. }
  375. return null;
  376. }
  377. /**
  378. * Sets the value indicating whether cookies should be used to store session IDs.
  379. *
  380. * Three states are possible:
  381. *
  382. * - true: cookies and only cookies will be used to store session IDs.
  383. * - false: cookies will not be used to store session IDs.
  384. * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
  385. *
  386. * @param bool|null $value the value indicating whether cookies should be used to store session IDs.
  387. */
  388. public function setUseCookies($value)
  389. {
  390. $this->freeze();
  391. if ($value === false) {
  392. ini_set('session.use_cookies', '0');
  393. ini_set('session.use_only_cookies', '0');
  394. } elseif ($value === true) {
  395. ini_set('session.use_cookies', '1');
  396. ini_set('session.use_only_cookies', '1');
  397. } else {
  398. ini_set('session.use_cookies', '1');
  399. ini_set('session.use_only_cookies', '0');
  400. }
  401. $this->unfreeze();
  402. }
  403. /**
  404. * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
  405. */
  406. public function getGCProbability()
  407. {
  408. return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
  409. }
  410. /**
  411. * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
  412. * @throws InvalidArgumentException if the value is not between 0 and 100.
  413. */
  414. public function setGCProbability($value)
  415. {
  416. $this->freeze();
  417. if ($value >= 0 && $value <= 100) {
  418. // percent * 21474837 / 2147483647 ≈ percent * 0.01
  419. ini_set('session.gc_probability', floor($value * 21474836.47));
  420. ini_set('session.gc_divisor', 2147483647);
  421. } else {
  422. throw new InvalidArgumentException('GCProbability must be a value between 0 and 100.');
  423. }
  424. $this->unfreeze();
  425. }
  426. /**
  427. * @return bool whether transparent sid support is enabled or not, defaults to false.
  428. */
  429. public function getUseTransparentSessionID()
  430. {
  431. return ini_get('session.use_trans_sid') == 1;
  432. }
  433. /**
  434. * @param bool $value whether transparent sid support is enabled or not.
  435. */
  436. public function setUseTransparentSessionID($value)
  437. {
  438. $this->freeze();
  439. ini_set('session.use_trans_sid', $value ? '1' : '0');
  440. $this->unfreeze();
  441. }
  442. /**
  443. * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up.
  444. * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
  445. */
  446. public function getTimeout()
  447. {
  448. return (int) ini_get('session.gc_maxlifetime');
  449. }
  450. /**
  451. * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
  452. */
  453. public function setTimeout($value)
  454. {
  455. $this->freeze();
  456. ini_set('session.gc_maxlifetime', $value);
  457. $this->unfreeze();
  458. }
  459. /**
  460. * Session open handler.
  461. * This method should be overridden if [[useCustomStorage]] returns true.
  462. * @internal Do not call this method directly.
  463. * @param string $savePath session save path
  464. * @param string $sessionName session name
  465. * @return bool whether session is opened successfully
  466. */
  467. public function openSession($savePath, $sessionName)
  468. {
  469. return true;
  470. }
  471. /**
  472. * Session close handler.
  473. * This method should be overridden if [[useCustomStorage]] returns true.
  474. * @internal Do not call this method directly.
  475. * @return bool whether session is closed successfully
  476. */
  477. public function closeSession()
  478. {
  479. return true;
  480. }
  481. /**
  482. * Session read handler.
  483. * This method should be overridden if [[useCustomStorage]] returns true.
  484. * @internal Do not call this method directly.
  485. * @param string $id session ID
  486. * @return string the session data
  487. */
  488. public function readSession($id)
  489. {
  490. return '';
  491. }
  492. /**
  493. * Session write handler.
  494. * This method should be overridden if [[useCustomStorage]] returns true.
  495. * @internal Do not call this method directly.
  496. * @param string $id session ID
  497. * @param string $data session data
  498. * @return bool whether session write is successful
  499. */
  500. public function writeSession($id, $data)
  501. {
  502. return true;
  503. }
  504. /**
  505. * Session destroy handler.
  506. * This method should be overridden if [[useCustomStorage]] returns true.
  507. * @internal Do not call this method directly.
  508. * @param string $id session ID
  509. * @return bool whether session is destroyed successfully
  510. */
  511. public function destroySession($id)
  512. {
  513. return true;
  514. }
  515. /**
  516. * Session GC (garbage collection) handler.
  517. * This method should be overridden if [[useCustomStorage]] returns true.
  518. * @internal Do not call this method directly.
  519. * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
  520. * @return bool whether session is GCed successfully
  521. */
  522. public function gcSession($maxLifetime)
  523. {
  524. return true;
  525. }
  526. /**
  527. * Returns an iterator for traversing the session variables.
  528. * This method is required by the interface [[\IteratorAggregate]].
  529. * @return SessionIterator an iterator for traversing the session variables.
  530. */
  531. public function getIterator()
  532. {
  533. $this->open();
  534. return new SessionIterator();
  535. }
  536. /**
  537. * Returns the number of items in the session.
  538. * @return int the number of session variables
  539. */
  540. public function getCount()
  541. {
  542. $this->open();
  543. return count($_SESSION);
  544. }
  545. /**
  546. * Returns the number of items in the session.
  547. * This method is required by [[\Countable]] interface.
  548. * @return int number of items in the session.
  549. */
  550. public function count()
  551. {
  552. return $this->getCount();
  553. }
  554. /**
  555. * Returns the session variable value with the session variable name.
  556. * If the session variable does not exist, the `$defaultValue` will be returned.
  557. * @param string $key the session variable name
  558. * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
  559. * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
  560. */
  561. public function get($key, $defaultValue = null)
  562. {
  563. $this->open();
  564. return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
  565. }
  566. /**
  567. * Adds a session variable.
  568. * If the specified name already exists, the old value will be overwritten.
  569. * @param string $key session variable name
  570. * @param mixed $value session variable value
  571. */
  572. public function set($key, $value)
  573. {
  574. $this->open();
  575. $_SESSION[$key] = $value;
  576. }
  577. /**
  578. * Removes a session variable.
  579. * @param string $key the name of the session variable to be removed
  580. * @return mixed the removed value, null if no such session variable.
  581. */
  582. public function remove($key)
  583. {
  584. $this->open();
  585. if (isset($_SESSION[$key])) {
  586. $value = $_SESSION[$key];
  587. unset($_SESSION[$key]);
  588. return $value;
  589. }
  590. return null;
  591. }
  592. /**
  593. * Removes all session variables.
  594. */
  595. public function removeAll()
  596. {
  597. $this->open();
  598. foreach (array_keys($_SESSION) as $key) {
  599. unset($_SESSION[$key]);
  600. }
  601. }
  602. /**
  603. * @param mixed $key session variable name
  604. * @return bool whether there is the named session variable
  605. */
  606. public function has($key)
  607. {
  608. $this->open();
  609. return isset($_SESSION[$key]);
  610. }
  611. /**
  612. * Updates the counters for flash messages and removes outdated flash messages.
  613. * This method should only be called once in [[init()]].
  614. */
  615. protected function updateFlashCounters()
  616. {
  617. $counters = $this->get($this->flashParam, []);
  618. if (is_array($counters)) {
  619. foreach ($counters as $key => $count) {
  620. if ($count > 0) {
  621. unset($counters[$key], $_SESSION[$key]);
  622. } elseif ($count == 0) {
  623. $counters[$key]++;
  624. }
  625. }
  626. $_SESSION[$this->flashParam] = $counters;
  627. } else {
  628. // fix the unexpected problem that flashParam doesn't return an array
  629. unset($_SESSION[$this->flashParam]);
  630. }
  631. }
  632. /**
  633. * Returns a flash message.
  634. * @param string $key the key identifying the flash message
  635. * @param mixed $defaultValue value to be returned if the flash message does not exist.
  636. * @param bool $delete whether to delete this flash message right after this method is called.
  637. * If false, the flash message will be automatically deleted in the next request.
  638. * @return mixed the flash message or an array of messages if addFlash was used
  639. * @see setFlash()
  640. * @see addFlash()
  641. * @see hasFlash()
  642. * @see getAllFlashes()
  643. * @see removeFlash()
  644. */
  645. public function getFlash($key, $defaultValue = null, $delete = false)
  646. {
  647. $counters = $this->get($this->flashParam, []);
  648. if (isset($counters[$key])) {
  649. $value = $this->get($key, $defaultValue);
  650. if ($delete) {
  651. $this->removeFlash($key);
  652. } elseif ($counters[$key] < 0) {
  653. // mark for deletion in the next request
  654. $counters[$key] = 1;
  655. $_SESSION[$this->flashParam] = $counters;
  656. }
  657. return $value;
  658. }
  659. return $defaultValue;
  660. }
  661. /**
  662. * Returns all flash messages.
  663. *
  664. * You may use this method to display all the flash messages in a view file:
  665. *
  666. * ```php
  667. * <?php
  668. * foreach (Yii::$app->session->getAllFlashes() as $key => $message) {
  669. * echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
  670. * } ?>
  671. * ```
  672. *
  673. * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
  674. * as the flash message key to influence the color of the div.
  675. *
  676. * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code.
  677. *
  678. * [bootstrap alert]: http://getbootstrap.com/components/#alerts
  679. *
  680. * @param bool $delete whether to delete the flash messages right after this method is called.
  681. * If false, the flash messages will be automatically deleted in the next request.
  682. * @return array flash messages (key => message or key => [message1, message2]).
  683. * @see setFlash()
  684. * @see addFlash()
  685. * @see getFlash()
  686. * @see hasFlash()
  687. * @see removeFlash()
  688. */
  689. public function getAllFlashes($delete = false)
  690. {
  691. $counters = $this->get($this->flashParam, []);
  692. $flashes = [];
  693. foreach (array_keys($counters) as $key) {
  694. if (array_key_exists($key, $_SESSION)) {
  695. $flashes[$key] = $_SESSION[$key];
  696. if ($delete) {
  697. unset($counters[$key], $_SESSION[$key]);
  698. } elseif ($counters[$key] < 0) {
  699. // mark for deletion in the next request
  700. $counters[$key] = 1;
  701. }
  702. } else {
  703. unset($counters[$key]);
  704. }
  705. }
  706. $_SESSION[$this->flashParam] = $counters;
  707. return $flashes;
  708. }
  709. /**
  710. * Sets a flash message.
  711. * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen
  712. * in the next request.
  713. * If there is already an existing flash message with the same key, it will be overwritten by the new one.
  714. * @param string $key the key identifying the flash message. Note that flash messages
  715. * and normal session variables share the same name space. If you have a normal
  716. * session variable using the same name, its value will be overwritten by this method.
  717. * @param mixed $value flash message
  718. * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
  719. * it is accessed. If false, the flash message will be automatically removed after the next request,
  720. * regardless if it is accessed or not. If true (default value), the flash message will remain until after
  721. * it is accessed.
  722. * @see getFlash()
  723. * @see addFlash()
  724. * @see removeFlash()
  725. */
  726. public function setFlash($key, $value = true, $removeAfterAccess = true)
  727. {
  728. $counters = $this->get($this->flashParam, []);
  729. $counters[$key] = $removeAfterAccess ? -1 : 0;
  730. $_SESSION[$key] = $value;
  731. $_SESSION[$this->flashParam] = $counters;
  732. }
  733. /**
  734. * Adds a flash message.
  735. * If there are existing flash messages with the same key, the new one will be appended to the existing message array.
  736. * @param string $key the key identifying the flash message.
  737. * @param mixed $value flash message
  738. * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
  739. * it is accessed. If false, the flash message will be automatically removed after the next request,
  740. * regardless if it is accessed or not. If true (default value), the flash message will remain until after
  741. * it is accessed.
  742. * @see getFlash()
  743. * @see setFlash()
  744. * @see removeFlash()
  745. */
  746. public function addFlash($key, $value = true, $removeAfterAccess = true)
  747. {
  748. $counters = $this->get($this->flashParam, []);
  749. $counters[$key] = $removeAfterAccess ? -1 : 0;
  750. $_SESSION[$this->flashParam] = $counters;
  751. if (empty($_SESSION[$key])) {
  752. $_SESSION[$key] = [$value];
  753. } elseif (is_array($_SESSION[$key])) {
  754. $_SESSION[$key][] = $value;
  755. } else {
  756. $_SESSION[$key] = [$_SESSION[$key], $value];
  757. }
  758. }
  759. /**
  760. * Removes a flash message.
  761. * @param string $key the key identifying the flash message. Note that flash messages
  762. * and normal session variables share the same name space. If you have a normal
  763. * session variable using the same name, it will be removed by this method.
  764. * @return mixed the removed flash message. Null if the flash message does not exist.
  765. * @see getFlash()
  766. * @see setFlash()
  767. * @see addFlash()
  768. * @see removeAllFlashes()
  769. */
  770. public function removeFlash($key)
  771. {
  772. $counters = $this->get($this->flashParam, []);
  773. $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
  774. unset($counters[$key], $_SESSION[$key]);
  775. $_SESSION[$this->flashParam] = $counters;
  776. return $value;
  777. }
  778. /**
  779. * Removes all flash messages.
  780. * Note that flash messages and normal session variables share the same name space.
  781. * If you have a normal session variable using the same name, it will be removed
  782. * by this method.
  783. * @see getFlash()
  784. * @see setFlash()
  785. * @see addFlash()
  786. * @see removeFlash()
  787. */
  788. public function removeAllFlashes()
  789. {
  790. $counters = $this->get($this->flashParam, []);
  791. foreach (array_keys($counters) as $key) {
  792. unset($_SESSION[$key]);
  793. }
  794. unset($_SESSION[$this->flashParam]);
  795. }
  796. /**
  797. * Returns a value indicating whether there are flash messages associated with the specified key.
  798. * @param string $key key identifying the flash message type
  799. * @return bool whether any flash messages exist under specified key
  800. */
  801. public function hasFlash($key)
  802. {
  803. return $this->getFlash($key) !== null;
  804. }
  805. /**
  806. * This method is required by the interface [[\ArrayAccess]].
  807. * @param mixed $offset the offset to check on
  808. * @return bool
  809. */
  810. public function offsetExists($offset)
  811. {
  812. $this->open();
  813. return isset($_SESSION[$offset]);
  814. }
  815. /**
  816. * This method is required by the interface [[\ArrayAccess]].
  817. * @param int $offset the offset to retrieve element.
  818. * @return mixed the element at the offset, null if no element is found at the offset
  819. */
  820. public function offsetGet($offset)
  821. {
  822. $this->open();
  823. return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
  824. }
  825. /**
  826. * This method is required by the interface [[\ArrayAccess]].
  827. * @param int $offset the offset to set element
  828. * @param mixed $item the element value
  829. */
  830. public function offsetSet($offset, $item)
  831. {
  832. $this->open();
  833. $_SESSION[$offset] = $item;
  834. }
  835. /**
  836. * This method is required by the interface [[\ArrayAccess]].
  837. * @param mixed $offset the offset to unset element
  838. */
  839. public function offsetUnset($offset)
  840. {
  841. $this->open();
  842. unset($_SESSION[$offset]);
  843. }
  844. /**
  845. * If session is started it's not possible to edit session ini settings. In PHP7.2+ it throws exception.
  846. * This function saves session data to temporary variable and stop session.
  847. * @since 2.0.14
  848. */
  849. protected function freeze()
  850. {
  851. if ($this->getIsActive()) {
  852. if (isset($_SESSION)) {
  853. $this->frozenSessionData = $_SESSION;
  854. }
  855. $this->close();
  856. Yii::info('Session frozen', __METHOD__);
  857. }
  858. }
  859. /**
  860. * Starts session and restores data from temporary variable
  861. * @since 2.0.14
  862. */
  863. protected function unfreeze()
  864. {
  865. if (null !== $this->frozenSessionData) {
  866. YII_DEBUG ? session_start() : @session_start();
  867. if ($this->getIsActive()) {
  868. Yii::info('Session unfrozen', __METHOD__);
  869. } else {
  870. $error = error_get_last();
  871. $message = isset($error['message']) ? $error['message'] : 'Failed to unfreeze session.';
  872. Yii::error($message, __METHOD__);
  873. }
  874. $_SESSION = $this->frozenSessionData;
  875. $this->frozenSessionData = null;
  876. }
  877. }
  878. /**
  879. * Set cache limiter
  880. *
  881. * @param string $cacheLimiter
  882. * @since 2.0.14
  883. */
  884. public function setCacheLimiter($cacheLimiter)
  885. {
  886. $this->freeze();
  887. session_cache_limiter($cacheLimiter);
  888. $this->unfreeze();
  889. }
  890. /**
  891. * Returns current cache limiter
  892. *
  893. * @return string current cache limiter
  894. * @since 2.0.14
  895. */
  896. public function getCacheLimiter()
  897. {
  898. return session_cache_limiter();
  899. }
  900. }