Session.php 37 KB

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