Session.php 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  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 https://secure.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()](https://secure.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()](https://secure.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 <https://secure.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()](https://secure.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()](https://secure.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()](https://secure.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()](https://secure.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 https://secure.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. * Starting with Yii 2.0.21 `sameSite` is also supported. It requires PHP version 7.3.0 or higher.
  342. * For securtiy, an exception will be thrown if `sameSite` is set while using an unsupported version of PHP.
  343. * To use this feature across different PHP versions check the version first. E.g.
  344. * ```php
  345. * [
  346. * 'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
  347. * ]
  348. * ```
  349. * See https://www.owasp.org/index.php/SameSite for more information about `sameSite`.
  350. *
  351. * @throws InvalidArgumentException if the parameters are incomplete.
  352. * @see https://secure.php.net/manual/en/function.session-set-cookie-params.php
  353. */
  354. public function setCookieParams(array $value)
  355. {
  356. $this->_cookieParams = $value;
  357. }
  358. /**
  359. * Sets the session cookie parameters.
  360. * This method is called by [[open()]] when it is about to open the session.
  361. * @throws InvalidArgumentException if the parameters are incomplete.
  362. * @see https://secure.php.net/manual/en/function.session-set-cookie-params.php
  363. */
  364. private function setCookieParamsInternal()
  365. {
  366. $data = $this->getCookieParams();
  367. if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) {
  368. if (PHP_VERSION_ID >= 70300) {
  369. session_set_cookie_params($data);
  370. } else {
  371. if (!empty($data['sameSite'])) {
  372. throw new InvalidConfigException('sameSite cookie is not supported by PHP versions < 7.3.0 (set it to null in this environment)');
  373. }
  374. session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
  375. }
  376. } else {
  377. throw new InvalidArgumentException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.');
  378. }
  379. }
  380. /**
  381. * Returns the value indicating whether cookies should be used to store session IDs.
  382. * @return bool|null the value indicating whether cookies should be used to store session IDs.
  383. * @see setUseCookies()
  384. */
  385. public function getUseCookies()
  386. {
  387. if (ini_get('session.use_cookies') === '0') {
  388. return false;
  389. } elseif (ini_get('session.use_only_cookies') === '1') {
  390. return true;
  391. }
  392. return null;
  393. }
  394. /**
  395. * Sets the value indicating whether cookies should be used to store session IDs.
  396. *
  397. * Three states are possible:
  398. *
  399. * - true: cookies and only cookies will be used to store session IDs.
  400. * - false: cookies will not be used to store session IDs.
  401. * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
  402. *
  403. * @param bool|null $value the value indicating whether cookies should be used to store session IDs.
  404. */
  405. public function setUseCookies($value)
  406. {
  407. $this->freeze();
  408. if ($value === false) {
  409. ini_set('session.use_cookies', '0');
  410. ini_set('session.use_only_cookies', '0');
  411. } elseif ($value === true) {
  412. ini_set('session.use_cookies', '1');
  413. ini_set('session.use_only_cookies', '1');
  414. } else {
  415. ini_set('session.use_cookies', '1');
  416. ini_set('session.use_only_cookies', '0');
  417. }
  418. $this->unfreeze();
  419. }
  420. /**
  421. * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
  422. */
  423. public function getGCProbability()
  424. {
  425. return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
  426. }
  427. /**
  428. * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
  429. * @throws InvalidArgumentException if the value is not between 0 and 100.
  430. */
  431. public function setGCProbability($value)
  432. {
  433. $this->freeze();
  434. if ($value >= 0 && $value <= 100) {
  435. // percent * 21474837 / 2147483647 ≈ percent * 0.01
  436. ini_set('session.gc_probability', floor($value * 21474836.47));
  437. ini_set('session.gc_divisor', 2147483647);
  438. } else {
  439. throw new InvalidArgumentException('GCProbability must be a value between 0 and 100.');
  440. }
  441. $this->unfreeze();
  442. }
  443. /**
  444. * @return bool whether transparent sid support is enabled or not, defaults to false.
  445. */
  446. public function getUseTransparentSessionID()
  447. {
  448. return ini_get('session.use_trans_sid') == 1;
  449. }
  450. /**
  451. * @param bool $value whether transparent sid support is enabled or not.
  452. */
  453. public function setUseTransparentSessionID($value)
  454. {
  455. $this->freeze();
  456. ini_set('session.use_trans_sid', $value ? '1' : '0');
  457. $this->unfreeze();
  458. }
  459. /**
  460. * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up.
  461. * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
  462. */
  463. public function getTimeout()
  464. {
  465. return (int) ini_get('session.gc_maxlifetime');
  466. }
  467. /**
  468. * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
  469. */
  470. public function setTimeout($value)
  471. {
  472. $this->freeze();
  473. ini_set('session.gc_maxlifetime', $value);
  474. $this->unfreeze();
  475. }
  476. /**
  477. * Session open handler.
  478. * This method should be overridden if [[useCustomStorage]] returns true.
  479. * @internal Do not call this method directly.
  480. * @param string $savePath session save path
  481. * @param string $sessionName session name
  482. * @return bool whether session is opened successfully
  483. */
  484. public function openSession($savePath, $sessionName)
  485. {
  486. return true;
  487. }
  488. /**
  489. * Session close handler.
  490. * This method should be overridden if [[useCustomStorage]] returns true.
  491. * @internal Do not call this method directly.
  492. * @return bool whether session is closed successfully
  493. */
  494. public function closeSession()
  495. {
  496. return true;
  497. }
  498. /**
  499. * Session read handler.
  500. * This method should be overridden if [[useCustomStorage]] returns true.
  501. * @internal Do not call this method directly.
  502. * @param string $id session ID
  503. * @return string the session data
  504. */
  505. public function readSession($id)
  506. {
  507. return '';
  508. }
  509. /**
  510. * Session write handler.
  511. * This method should be overridden if [[useCustomStorage]] returns true.
  512. * @internal Do not call this method directly.
  513. * @param string $id session ID
  514. * @param string $data session data
  515. * @return bool whether session write is successful
  516. */
  517. public function writeSession($id, $data)
  518. {
  519. return true;
  520. }
  521. /**
  522. * Session destroy handler.
  523. * This method should be overridden if [[useCustomStorage]] returns true.
  524. * @internal Do not call this method directly.
  525. * @param string $id session ID
  526. * @return bool whether session is destroyed successfully
  527. */
  528. public function destroySession($id)
  529. {
  530. return true;
  531. }
  532. /**
  533. * Session GC (garbage collection) handler.
  534. * This method should be overridden if [[useCustomStorage]] returns true.
  535. * @internal Do not call this method directly.
  536. * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
  537. * @return bool whether session is GCed successfully
  538. */
  539. public function gcSession($maxLifetime)
  540. {
  541. return true;
  542. }
  543. /**
  544. * Returns an iterator for traversing the session variables.
  545. * This method is required by the interface [[\IteratorAggregate]].
  546. * @return SessionIterator an iterator for traversing the session variables.
  547. */
  548. public function getIterator()
  549. {
  550. $this->open();
  551. return new SessionIterator();
  552. }
  553. /**
  554. * Returns the number of items in the session.
  555. * @return int the number of session variables
  556. */
  557. public function getCount()
  558. {
  559. $this->open();
  560. return count($_SESSION);
  561. }
  562. /**
  563. * Returns the number of items in the session.
  564. * This method is required by [[\Countable]] interface.
  565. * @return int number of items in the session.
  566. */
  567. public function count()
  568. {
  569. return $this->getCount();
  570. }
  571. /**
  572. * Returns the session variable value with the session variable name.
  573. * If the session variable does not exist, the `$defaultValue` will be returned.
  574. * @param string $key the session variable name
  575. * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
  576. * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
  577. */
  578. public function get($key, $defaultValue = null)
  579. {
  580. $this->open();
  581. return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
  582. }
  583. /**
  584. * Adds a session variable.
  585. * If the specified name already exists, the old value will be overwritten.
  586. * @param string $key session variable name
  587. * @param mixed $value session variable value
  588. */
  589. public function set($key, $value)
  590. {
  591. $this->open();
  592. $_SESSION[$key] = $value;
  593. }
  594. /**
  595. * Removes a session variable.
  596. * @param string $key the name of the session variable to be removed
  597. * @return mixed the removed value, null if no such session variable.
  598. */
  599. public function remove($key)
  600. {
  601. $this->open();
  602. if (isset($_SESSION[$key])) {
  603. $value = $_SESSION[$key];
  604. unset($_SESSION[$key]);
  605. return $value;
  606. }
  607. return null;
  608. }
  609. /**
  610. * Removes all session variables.
  611. */
  612. public function removeAll()
  613. {
  614. $this->open();
  615. foreach (array_keys($_SESSION) as $key) {
  616. unset($_SESSION[$key]);
  617. }
  618. }
  619. /**
  620. * @param mixed $key session variable name
  621. * @return bool whether there is the named session variable
  622. */
  623. public function has($key)
  624. {
  625. $this->open();
  626. return isset($_SESSION[$key]);
  627. }
  628. /**
  629. * Updates the counters for flash messages and removes outdated flash messages.
  630. * This method should only be called once in [[init()]].
  631. */
  632. protected function updateFlashCounters()
  633. {
  634. $counters = $this->get($this->flashParam, []);
  635. if (is_array($counters)) {
  636. foreach ($counters as $key => $count) {
  637. if ($count > 0) {
  638. unset($counters[$key], $_SESSION[$key]);
  639. } elseif ($count == 0) {
  640. $counters[$key]++;
  641. }
  642. }
  643. $_SESSION[$this->flashParam] = $counters;
  644. } else {
  645. // fix the unexpected problem that flashParam doesn't return an array
  646. unset($_SESSION[$this->flashParam]);
  647. }
  648. }
  649. /**
  650. * Returns a flash message.
  651. * @param string $key the key identifying the flash message
  652. * @param mixed $defaultValue value to be returned if the flash message does not exist.
  653. * @param bool $delete whether to delete this flash message right after this method is called.
  654. * If false, the flash message will be automatically deleted in the next request.
  655. * @return mixed the flash message or an array of messages if addFlash was used
  656. * @see setFlash()
  657. * @see addFlash()
  658. * @see hasFlash()
  659. * @see getAllFlashes()
  660. * @see removeFlash()
  661. */
  662. public function getFlash($key, $defaultValue = null, $delete = false)
  663. {
  664. $counters = $this->get($this->flashParam, []);
  665. if (isset($counters[$key])) {
  666. $value = $this->get($key, $defaultValue);
  667. if ($delete) {
  668. $this->removeFlash($key);
  669. } elseif ($counters[$key] < 0) {
  670. // mark for deletion in the next request
  671. $counters[$key] = 1;
  672. $_SESSION[$this->flashParam] = $counters;
  673. }
  674. return $value;
  675. }
  676. return $defaultValue;
  677. }
  678. /**
  679. * Returns all flash messages.
  680. *
  681. * You may use this method to display all the flash messages in a view file:
  682. *
  683. * ```php
  684. * <?php
  685. * foreach (Yii::$app->session->getAllFlashes() as $key => $message) {
  686. * echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
  687. * } ?>
  688. * ```
  689. *
  690. * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
  691. * as the flash message key to influence the color of the div.
  692. *
  693. * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code.
  694. *
  695. * [bootstrap alert]: http://getbootstrap.com/components/#alerts
  696. *
  697. * @param bool $delete whether to delete the flash messages right after this method is called.
  698. * If false, the flash messages will be automatically deleted in the next request.
  699. * @return array flash messages (key => message or key => [message1, message2]).
  700. * @see setFlash()
  701. * @see addFlash()
  702. * @see getFlash()
  703. * @see hasFlash()
  704. * @see removeFlash()
  705. */
  706. public function getAllFlashes($delete = false)
  707. {
  708. $counters = $this->get($this->flashParam, []);
  709. $flashes = [];
  710. foreach (array_keys($counters) as $key) {
  711. if (array_key_exists($key, $_SESSION)) {
  712. $flashes[$key] = $_SESSION[$key];
  713. if ($delete) {
  714. unset($counters[$key], $_SESSION[$key]);
  715. } elseif ($counters[$key] < 0) {
  716. // mark for deletion in the next request
  717. $counters[$key] = 1;
  718. }
  719. } else {
  720. unset($counters[$key]);
  721. }
  722. }
  723. $_SESSION[$this->flashParam] = $counters;
  724. return $flashes;
  725. }
  726. /**
  727. * Sets a flash message.
  728. * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen
  729. * in the next request.
  730. * If there is already an existing flash message with the same key, it will be overwritten by the new one.
  731. * @param string $key the key identifying the flash message. Note that flash messages
  732. * and normal session variables share the same name space. If you have a normal
  733. * session variable using the same name, its value will be overwritten by this method.
  734. * @param mixed $value flash message
  735. * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
  736. * it is accessed. If false, the flash message will be automatically removed after the next request,
  737. * regardless if it is accessed or not. If true (default value), the flash message will remain until after
  738. * it is accessed.
  739. * @see getFlash()
  740. * @see addFlash()
  741. * @see removeFlash()
  742. */
  743. public function setFlash($key, $value = true, $removeAfterAccess = true)
  744. {
  745. $counters = $this->get($this->flashParam, []);
  746. $counters[$key] = $removeAfterAccess ? -1 : 0;
  747. $_SESSION[$key] = $value;
  748. $_SESSION[$this->flashParam] = $counters;
  749. }
  750. /**
  751. * Adds a flash message.
  752. * If there are existing flash messages with the same key, the new one will be appended to the existing message array.
  753. * @param string $key the key identifying the flash message.
  754. * @param mixed $value flash message
  755. * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
  756. * it is accessed. If false, the flash message will be automatically removed after the next request,
  757. * regardless if it is accessed or not. If true (default value), the flash message will remain until after
  758. * it is accessed.
  759. * @see getFlash()
  760. * @see setFlash()
  761. * @see removeFlash()
  762. */
  763. public function addFlash($key, $value = true, $removeAfterAccess = true)
  764. {
  765. $counters = $this->get($this->flashParam, []);
  766. $counters[$key] = $removeAfterAccess ? -1 : 0;
  767. $_SESSION[$this->flashParam] = $counters;
  768. if (empty($_SESSION[$key])) {
  769. $_SESSION[$key] = [$value];
  770. } elseif (is_array($_SESSION[$key])) {
  771. $_SESSION[$key][] = $value;
  772. } else {
  773. $_SESSION[$key] = [$_SESSION[$key], $value];
  774. }
  775. }
  776. /**
  777. * Removes a flash message.
  778. * @param string $key the key identifying the flash message. Note that flash messages
  779. * and normal session variables share the same name space. If you have a normal
  780. * session variable using the same name, it will be removed by this method.
  781. * @return mixed the removed flash message. Null if the flash message does not exist.
  782. * @see getFlash()
  783. * @see setFlash()
  784. * @see addFlash()
  785. * @see removeAllFlashes()
  786. */
  787. public function removeFlash($key)
  788. {
  789. $counters = $this->get($this->flashParam, []);
  790. $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
  791. unset($counters[$key], $_SESSION[$key]);
  792. $_SESSION[$this->flashParam] = $counters;
  793. return $value;
  794. }
  795. /**
  796. * Removes all flash messages.
  797. * Note that flash messages and normal session variables share the same name space.
  798. * If you have a normal session variable using the same name, it will be removed
  799. * by this method.
  800. * @see getFlash()
  801. * @see setFlash()
  802. * @see addFlash()
  803. * @see removeFlash()
  804. */
  805. public function removeAllFlashes()
  806. {
  807. $counters = $this->get($this->flashParam, []);
  808. foreach (array_keys($counters) as $key) {
  809. unset($_SESSION[$key]);
  810. }
  811. unset($_SESSION[$this->flashParam]);
  812. }
  813. /**
  814. * Returns a value indicating whether there are flash messages associated with the specified key.
  815. * @param string $key key identifying the flash message type
  816. * @return bool whether any flash messages exist under specified key
  817. */
  818. public function hasFlash($key)
  819. {
  820. return $this->getFlash($key) !== null;
  821. }
  822. /**
  823. * This method is required by the interface [[\ArrayAccess]].
  824. * @param mixed $offset the offset to check on
  825. * @return bool
  826. */
  827. public function offsetExists($offset)
  828. {
  829. $this->open();
  830. return isset($_SESSION[$offset]);
  831. }
  832. /**
  833. * This method is required by the interface [[\ArrayAccess]].
  834. * @param int $offset the offset to retrieve element.
  835. * @return mixed the element at the offset, null if no element is found at the offset
  836. */
  837. public function offsetGet($offset)
  838. {
  839. $this->open();
  840. return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
  841. }
  842. /**
  843. * This method is required by the interface [[\ArrayAccess]].
  844. * @param int $offset the offset to set element
  845. * @param mixed $item the element value
  846. */
  847. public function offsetSet($offset, $item)
  848. {
  849. $this->open();
  850. $_SESSION[$offset] = $item;
  851. }
  852. /**
  853. * This method is required by the interface [[\ArrayAccess]].
  854. * @param mixed $offset the offset to unset element
  855. */
  856. public function offsetUnset($offset)
  857. {
  858. $this->open();
  859. unset($_SESSION[$offset]);
  860. }
  861. /**
  862. * If session is started it's not possible to edit session ini settings. In PHP7.2+ it throws exception.
  863. * This function saves session data to temporary variable and stop session.
  864. * @since 2.0.14
  865. */
  866. protected function freeze()
  867. {
  868. if ($this->getIsActive()) {
  869. if (isset($_SESSION)) {
  870. $this->frozenSessionData = $_SESSION;
  871. }
  872. $this->close();
  873. Yii::info('Session frozen', __METHOD__);
  874. }
  875. }
  876. /**
  877. * Starts session and restores data from temporary variable
  878. * @since 2.0.14
  879. */
  880. protected function unfreeze()
  881. {
  882. if (null !== $this->frozenSessionData) {
  883. YII_DEBUG ? session_start() : @session_start();
  884. if ($this->getIsActive()) {
  885. Yii::info('Session unfrozen', __METHOD__);
  886. } else {
  887. $error = error_get_last();
  888. $message = isset($error['message']) ? $error['message'] : 'Failed to unfreeze session.';
  889. Yii::error($message, __METHOD__);
  890. }
  891. $_SESSION = $this->frozenSessionData;
  892. $this->frozenSessionData = null;
  893. }
  894. }
  895. /**
  896. * Set cache limiter
  897. *
  898. * @param string $cacheLimiter
  899. * @since 2.0.14
  900. */
  901. public function setCacheLimiter($cacheLimiter)
  902. {
  903. $this->freeze();
  904. session_cache_limiter($cacheLimiter);
  905. $this->unfreeze();
  906. }
  907. /**
  908. * Returns current cache limiter
  909. *
  910. * @return string current cache limiter
  911. * @since 2.0.14
  912. */
  913. public function getCacheLimiter()
  914. {
  915. return session_cache_limiter();
  916. }
  917. }