Session.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  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\InvalidConfigException;
  11. use yii\base\InvalidParamException;
  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. * By default, [[autoStart]] is true which means the session will be started automatically
  21. * when the session component is accessed the first time.
  22. *
  23. * Session can be used like an array to set and get session data. For example,
  24. *
  25. * ~~~
  26. * $session = new Session;
  27. * $session->open();
  28. * $value1 = $session['name1']; // get session variable 'name1'
  29. * $value2 = $session['name2']; // get session variable 'name2'
  30. * foreach ($session as $name => $value) // traverse all session variables
  31. * $session['name3'] = $value3; // set session variable 'name3'
  32. * ~~~
  33. *
  34. * Session can be extended to support customized session storage.
  35. * To do so, override [[useCustomStorage()]] so that it returns true, and
  36. * override these methods with the actual logic about using custom storage:
  37. * [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
  38. * [[destroySession()]] and [[gcSession()]].
  39. *
  40. * Session also supports a special type of session data, called *flash messages*.
  41. * A flash message is available only in the current request and the next request.
  42. * After that, it will be deleted automatically. Flash messages are particularly
  43. * useful for displaying confirmation messages. To use flash messages, simply
  44. * call methods such as [[setFlash()]], [[getFlash()]].
  45. *
  46. * @property array $allFlashes Flash messages (key => message). This property is read-only.
  47. * @property array $cookieParams The session cookie parameters. This property is read-only.
  48. * @property integer $count The number of session variables. This property is read-only.
  49. * @property string $flash The key identifying the flash message. Note that flash messages and normal session
  50. * variables share the same name space. If you have a normal session variable using the same name, its value will
  51. * be overwritten by this method. This property is write-only.
  52. * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is
  53. * started on every session initialization, defaults to 1 meaning 1% chance.
  54. * @property string $id The current session ID.
  55. * @property boolean $isActive Whether the session has started. This property is read-only.
  56. * @property SessionIterator $iterator An iterator for traversing the session variables. This property is
  57. * read-only.
  58. * @property string $name The current session name.
  59. * @property string $savePath The current session save path, defaults to '/tmp'.
  60. * @property integer $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up.
  61. * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
  62. * @property boolean|null $useCookies The value indicating whether cookies should be used to store session
  63. * IDs.
  64. * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only.
  65. * @property boolean $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
  66. * false.
  67. *
  68. * @author Qiang Xue <[email protected]>
  69. * @since 2.0
  70. */
  71. class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
  72. {
  73. /**
  74. * @var boolean whether the session should be automatically started when the session component is initialized.
  75. */
  76. public $autoStart = true;
  77. /**
  78. * @var string the name of the session variable that stores the flash message data.
  79. */
  80. public $flashVar = '__flash';
  81. /**
  82. * @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.
  83. */
  84. public $handler;
  85. /**
  86. * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
  87. * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httpOnly'
  88. * @see http://www.php.net/manual/en/function.session-set-cookie-params.php
  89. */
  90. private $_cookieParams = ['httpOnly' => true];
  91. /**
  92. * Initializes the application component.
  93. * This method is required by IApplicationComponent and is invoked by application.
  94. */
  95. public function init()
  96. {
  97. parent::init();
  98. if ($this->autoStart) {
  99. $this->open();
  100. }
  101. register_shutdown_function([$this, 'close']);
  102. }
  103. /**
  104. * Returns a value indicating whether to use custom session storage.
  105. * This method should be overridden to return true by child classes that implement custom session storage.
  106. * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
  107. * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
  108. * @return boolean whether to use custom storage.
  109. */
  110. public function getUseCustomStorage()
  111. {
  112. return false;
  113. }
  114. /**
  115. * Starts the session.
  116. */
  117. public function open()
  118. {
  119. if (session_status() == PHP_SESSION_ACTIVE) {
  120. return;
  121. }
  122. if ($this->handler !== null) {
  123. if (!is_object($this->handler)) {
  124. $this->handler = Yii::createObject($this->handler);
  125. }
  126. if (!$this->handler instanceof \SessionHandlerInterface) {
  127. throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
  128. }
  129. @session_set_save_handler($this->handler, false);
  130. } elseif ($this->getUseCustomStorage()) {
  131. @session_set_save_handler(
  132. [$this, 'openSession'],
  133. [$this, 'closeSession'],
  134. [$this, 'readSession'],
  135. [$this, 'writeSession'],
  136. [$this, 'destroySession'],
  137. [$this, 'gcSession']
  138. );
  139. }
  140. $this->setCookieParamsInternal();
  141. @session_start();
  142. if (session_id() == '') {
  143. $error = error_get_last();
  144. $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
  145. Yii::error($message, __METHOD__);
  146. } else {
  147. $this->updateFlashCounters();
  148. }
  149. }
  150. /**
  151. * Ends the current session and store session data.
  152. */
  153. public function close()
  154. {
  155. if (session_id() !== '') {
  156. @session_write_close();
  157. }
  158. }
  159. /**
  160. * Frees all session variables and destroys all data registered to a session.
  161. */
  162. public function destroy()
  163. {
  164. if (session_id() !== '') {
  165. @session_unset();
  166. @session_destroy();
  167. }
  168. }
  169. /**
  170. * @return boolean whether the session has started
  171. */
  172. public function getIsActive()
  173. {
  174. return session_status() == PHP_SESSION_ACTIVE;
  175. }
  176. /**
  177. * @return string the current session ID
  178. */
  179. public function getId()
  180. {
  181. return session_id();
  182. }
  183. /**
  184. * @param string $value the session ID for the current session
  185. */
  186. public function setId($value)
  187. {
  188. session_id($value);
  189. }
  190. /**
  191. * Updates the current session ID with a newly generated one .
  192. * Please refer to <http://php.net/session_regenerate_id> for more details.
  193. * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
  194. */
  195. public function regenerateID($deleteOldSession = false)
  196. {
  197. session_regenerate_id($deleteOldSession);
  198. }
  199. /**
  200. * @return string the current session name
  201. */
  202. public function getName()
  203. {
  204. return session_name();
  205. }
  206. /**
  207. * @param string $value the session name for the current session, must be an alphanumeric string.
  208. * It defaults to "PHPSESSID".
  209. */
  210. public function setName($value)
  211. {
  212. session_name($value);
  213. }
  214. /**
  215. * @return string the current session save path, defaults to '/tmp'.
  216. */
  217. public function getSavePath()
  218. {
  219. return session_save_path();
  220. }
  221. /**
  222. * @param string $value the current session save path. This can be either a directory name or a path alias.
  223. * @throws InvalidParamException if the path is not a valid directory
  224. */
  225. public function setSavePath($value)
  226. {
  227. $path = Yii::getAlias($value);
  228. if (is_dir($path)) {
  229. session_save_path($path);
  230. } else {
  231. throw new InvalidParamException("Session save path is not a valid directory: $value");
  232. }
  233. }
  234. /**
  235. * @return array the session cookie parameters.
  236. * @see http://us2.php.net/manual/en/function.session-get-cookie-params.php
  237. */
  238. public function getCookieParams()
  239. {
  240. $params = session_get_cookie_params();
  241. if (isset($params['httponly'])) {
  242. $params['httpOnly'] = $params['httponly'];
  243. unset($params['httponly']);
  244. }
  245. return array_merge($params, $this->_cookieParams);
  246. }
  247. /**
  248. * Sets the session cookie parameters.
  249. * The cookie parameters passed to this method will be merged with the result
  250. * of `session_get_cookie_params()`.
  251. * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`.
  252. * @throws InvalidParamException if the parameters are incomplete.
  253. * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
  254. */
  255. public function setCookieParams(array $value)
  256. {
  257. $this->_cookieParams = $value;
  258. }
  259. /**
  260. * Sets the session cookie parameters.
  261. * This method is called by [[open()]] when it is about to open the session.
  262. * @throws InvalidParamException if the parameters are incomplete.
  263. * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
  264. */
  265. private function setCookieParamsInternal()
  266. {
  267. $data = $this->getCookieParams();
  268. extract($data);
  269. if (isset($lifetime, $path, $domain, $secure, $httpOnly)) {
  270. session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
  271. } else {
  272. throw new InvalidParamException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httpOnly.');
  273. }
  274. }
  275. /**
  276. * Returns the value indicating whether cookies should be used to store session IDs.
  277. * @return boolean|null the value indicating whether cookies should be used to store session IDs.
  278. * @see setUseCookies()
  279. */
  280. public function getUseCookies()
  281. {
  282. if (ini_get('session.use_cookies') === '0') {
  283. return false;
  284. } elseif (ini_get('session.use_only_cookies') === '1') {
  285. return true;
  286. } else {
  287. return null;
  288. }
  289. }
  290. /**
  291. * Sets the value indicating whether cookies should be used to store session IDs.
  292. * Three states are possible:
  293. *
  294. * - true: cookies and only cookies will be used to store session IDs.
  295. * - false: cookies will not be used to store session IDs.
  296. * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
  297. *
  298. * @param boolean|null $value the value indicating whether cookies should be used to store session IDs.
  299. */
  300. public function setUseCookies($value)
  301. {
  302. if ($value === false) {
  303. ini_set('session.use_cookies', '0');
  304. ini_set('session.use_only_cookies', '0');
  305. } elseif ($value === true) {
  306. ini_set('session.use_cookies', '1');
  307. ini_set('session.use_only_cookies', '1');
  308. } else {
  309. ini_set('session.use_cookies', '1');
  310. ini_set('session.use_only_cookies', '0');
  311. }
  312. }
  313. /**
  314. * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
  315. */
  316. public function getGCProbability()
  317. {
  318. return (float)(ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
  319. }
  320. /**
  321. * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
  322. * @throws InvalidParamException if the value is not between 0 and 100.
  323. */
  324. public function setGCProbability($value)
  325. {
  326. if ($value >= 0 && $value <= 100) {
  327. // percent * 21474837 / 2147483647 ≈ percent * 0.01
  328. ini_set('session.gc_probability', floor($value * 21474836.47));
  329. ini_set('session.gc_divisor', 2147483647);
  330. } else {
  331. throw new InvalidParamException('GCProbability must be a value between 0 and 100.');
  332. }
  333. }
  334. /**
  335. * @return boolean whether transparent sid support is enabled or not, defaults to false.
  336. */
  337. public function getUseTransparentSessionID()
  338. {
  339. return ini_get('session.use_trans_sid') == 1;
  340. }
  341. /**
  342. * @param boolean $value whether transparent sid support is enabled or not.
  343. */
  344. public function setUseTransparentSessionID($value)
  345. {
  346. ini_set('session.use_trans_sid', $value ? '1' : '0');
  347. }
  348. /**
  349. * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up.
  350. * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
  351. */
  352. public function getTimeout()
  353. {
  354. return (int)ini_get('session.gc_maxlifetime');
  355. }
  356. /**
  357. * @param integer $value the number of seconds after which data will be seen as 'garbage' and cleaned up
  358. */
  359. public function setTimeout($value)
  360. {
  361. ini_set('session.gc_maxlifetime', $value);
  362. }
  363. /**
  364. * Session open handler.
  365. * This method should be overridden if [[useCustomStorage()]] returns true.
  366. * Do not call this method directly.
  367. * @param string $savePath session save path
  368. * @param string $sessionName session name
  369. * @return boolean whether session is opened successfully
  370. */
  371. public function openSession($savePath, $sessionName)
  372. {
  373. return true;
  374. }
  375. /**
  376. * Session close handler.
  377. * This method should be overridden if [[useCustomStorage()]] returns true.
  378. * Do not call this method directly.
  379. * @return boolean whether session is closed successfully
  380. */
  381. public function closeSession()
  382. {
  383. return true;
  384. }
  385. /**
  386. * Session read handler.
  387. * This method should be overridden if [[useCustomStorage()]] returns true.
  388. * Do not call this method directly.
  389. * @param string $id session ID
  390. * @return string the session data
  391. */
  392. public function readSession($id)
  393. {
  394. return '';
  395. }
  396. /**
  397. * Session write handler.
  398. * This method should be overridden if [[useCustomStorage()]] returns true.
  399. * Do not call this method directly.
  400. * @param string $id session ID
  401. * @param string $data session data
  402. * @return boolean whether session write is successful
  403. */
  404. public function writeSession($id, $data)
  405. {
  406. return true;
  407. }
  408. /**
  409. * Session destroy handler.
  410. * This method should be overridden if [[useCustomStorage()]] returns true.
  411. * Do not call this method directly.
  412. * @param string $id session ID
  413. * @return boolean whether session is destroyed successfully
  414. */
  415. public function destroySession($id)
  416. {
  417. return true;
  418. }
  419. /**
  420. * Session GC (garbage collection) handler.
  421. * This method should be overridden if [[useCustomStorage()]] returns true.
  422. * Do not call this method directly.
  423. * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
  424. * @return boolean whether session is GCed successfully
  425. */
  426. public function gcSession($maxLifetime)
  427. {
  428. return true;
  429. }
  430. /**
  431. * Returns an iterator for traversing the session variables.
  432. * This method is required by the interface IteratorAggregate.
  433. * @return SessionIterator an iterator for traversing the session variables.
  434. */
  435. public function getIterator()
  436. {
  437. return new SessionIterator;
  438. }
  439. /**
  440. * Returns the number of items in the session.
  441. * @return integer the number of session variables
  442. */
  443. public function getCount()
  444. {
  445. return count($_SESSION);
  446. }
  447. /**
  448. * Returns the number of items in the session.
  449. * This method is required by Countable interface.
  450. * @return integer number of items in the session.
  451. */
  452. public function count()
  453. {
  454. return $this->getCount();
  455. }
  456. /**
  457. * Returns the session variable value with the session variable name.
  458. * If the session variable does not exist, the `$defaultValue` will be returned.
  459. * @param string $key the session variable name
  460. * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
  461. * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
  462. */
  463. public function get($key, $defaultValue = null)
  464. {
  465. return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
  466. }
  467. /**
  468. * Adds a session variable.
  469. * If the specified name already exists, the old value will be overwritten.
  470. * @param string $key session variable name
  471. * @param mixed $value session variable value
  472. */
  473. public function set($key, $value)
  474. {
  475. $_SESSION[$key] = $value;
  476. }
  477. /**
  478. * Removes a session variable.
  479. * @param string $key the name of the session variable to be removed
  480. * @return mixed the removed value, null if no such session variable.
  481. */
  482. public function remove($key)
  483. {
  484. if (isset($_SESSION[$key])) {
  485. $value = $_SESSION[$key];
  486. unset($_SESSION[$key]);
  487. return $value;
  488. } else {
  489. return null;
  490. }
  491. }
  492. /**
  493. * Removes all session variables
  494. */
  495. public function removeAll()
  496. {
  497. foreach (array_keys($_SESSION) as $key) {
  498. unset($_SESSION[$key]);
  499. }
  500. }
  501. /**
  502. * @param mixed $key session variable name
  503. * @return boolean whether there is the named session variable
  504. */
  505. public function has($key)
  506. {
  507. return isset($_SESSION[$key]);
  508. }
  509. /**
  510. * @return array the list of all session variables in array
  511. */
  512. public function toArray()
  513. {
  514. return $_SESSION;
  515. }
  516. /**
  517. * Updates the counters for flash messages and removes outdated flash messages.
  518. * This method should only be called once in [[init()]].
  519. */
  520. protected function updateFlashCounters()
  521. {
  522. $counters = $this->get($this->flashVar, []);
  523. if (is_array($counters)) {
  524. foreach ($counters as $key => $count) {
  525. if ($count) {
  526. unset($counters[$key], $_SESSION[$key]);
  527. } else {
  528. $counters[$key]++;
  529. }
  530. }
  531. $_SESSION[$this->flashVar] = $counters;
  532. } else {
  533. // fix the unexpected problem that flashVar doesn't return an array
  534. unset($_SESSION[$this->flashVar]);
  535. }
  536. }
  537. /**
  538. * Returns a flash message.
  539. * A flash message is available only in the current request and the next request.
  540. * @param string $key the key identifying the flash message
  541. * @param mixed $defaultValue value to be returned if the flash message does not exist.
  542. * @param boolean $delete whether to delete this flash message right after this method is called.
  543. * If false, the flash message will be automatically deleted after the next request.
  544. * @return mixed the flash message
  545. */
  546. public function getFlash($key, $defaultValue = null, $delete = false)
  547. {
  548. $counters = $this->get($this->flashVar, []);
  549. if (isset($counters[$key])) {
  550. $value = $this->get($key, $defaultValue);
  551. if ($delete) {
  552. $this->removeFlash($key);
  553. }
  554. return $value;
  555. } else {
  556. return $defaultValue;
  557. }
  558. }
  559. /**
  560. * Returns all flash messages.
  561. * @return array flash messages (key => message).
  562. */
  563. public function getAllFlashes()
  564. {
  565. $counters = $this->get($this->flashVar, []);
  566. $flashes = [];
  567. foreach (array_keys($counters) as $key) {
  568. if (isset($_SESSION[$key])) {
  569. $flashes[$key] = $_SESSION[$key];
  570. }
  571. }
  572. return $flashes;
  573. }
  574. /**
  575. * Stores a flash message.
  576. * A flash message is available only in the current request and the next request.
  577. * @param string $key the key identifying the flash message. Note that flash messages
  578. * and normal session variables share the same name space. If you have a normal
  579. * session variable using the same name, its value will be overwritten by this method.
  580. * @param mixed $value flash message
  581. */
  582. public function setFlash($key, $value = true)
  583. {
  584. $counters = $this->get($this->flashVar, []);
  585. $counters[$key] = 0;
  586. $_SESSION[$key] = $value;
  587. $_SESSION[$this->flashVar] = $counters;
  588. }
  589. /**
  590. * Removes a flash message.
  591. * Note that flash messages will be automatically removed after the next request.
  592. * @param string $key the key identifying the flash message. Note that flash messages
  593. * and normal session variables share the same name space. If you have a normal
  594. * session variable using the same name, it will be removed by this method.
  595. * @return mixed the removed flash message. Null if the flash message does not exist.
  596. */
  597. public function removeFlash($key)
  598. {
  599. $counters = $this->get($this->flashVar, []);
  600. $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
  601. unset($counters[$key], $_SESSION[$key]);
  602. $_SESSION[$this->flashVar] = $counters;
  603. return $value;
  604. }
  605. /**
  606. * Removes all flash messages.
  607. * Note that flash messages and normal session variables share the same name space.
  608. * If you have a normal session variable using the same name, it will be removed
  609. * by this method.
  610. */
  611. public function removeAllFlashes()
  612. {
  613. $counters = $this->get($this->flashVar, []);
  614. foreach (array_keys($counters) as $key) {
  615. unset($_SESSION[$key]);
  616. }
  617. unset($_SESSION[$this->flashVar]);
  618. }
  619. /**
  620. * Returns a value indicating whether there is a flash message associated with the specified key.
  621. * @param string $key key identifying the flash message
  622. * @return boolean whether the specified flash message exists
  623. */
  624. public function hasFlash($key)
  625. {
  626. return $this->getFlash($key) !== null;
  627. }
  628. /**
  629. * This method is required by the interface ArrayAccess.
  630. * @param mixed $offset the offset to check on
  631. * @return boolean
  632. */
  633. public function offsetExists($offset)
  634. {
  635. return isset($_SESSION[$offset]);
  636. }
  637. /**
  638. * This method is required by the interface ArrayAccess.
  639. * @param integer $offset the offset to retrieve element.
  640. * @return mixed the element at the offset, null if no element is found at the offset
  641. */
  642. public function offsetGet($offset)
  643. {
  644. return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
  645. }
  646. /**
  647. * This method is required by the interface ArrayAccess.
  648. * @param integer $offset the offset to set element
  649. * @param mixed $item the element value
  650. */
  651. public function offsetSet($offset, $item)
  652. {
  653. $_SESSION[$offset] = $item;
  654. }
  655. /**
  656. * This method is required by the interface ArrayAccess.
  657. * @param mixed $offset the offset to unset element
  658. */
  659. public function offsetUnset($offset)
  660. {
  661. unset($_SESSION[$offset]);
  662. }
  663. }