Session.php 37 KB

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