Session.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. namespace think;
  12. use think\exception\ClassNotFoundException;
  13. class Session
  14. {
  15. /**
  16. * 配置参数
  17. * @var array
  18. */
  19. protected $config = [];
  20. /**
  21. * 前缀
  22. * @var string
  23. */
  24. protected $prefix = '';
  25. /**
  26. * 是否初始化
  27. * @var bool
  28. */
  29. protected $init = null;
  30. /**
  31. * 锁驱动
  32. * @var object
  33. */
  34. protected $lockDriver = null;
  35. /**
  36. * 锁key
  37. * @var string
  38. */
  39. protected $sessKey = 'PHPSESSID';
  40. /**
  41. * 锁超时时间
  42. * @var integer
  43. */
  44. protected $lockTimeout = 3;
  45. /**
  46. * 是否启用锁机制
  47. * @var bool
  48. */
  49. protected $lock = false;
  50. public function __construct(array $config = [])
  51. {
  52. $this->config = $config;
  53. }
  54. /**
  55. * 设置或者获取session作用域(前缀)
  56. * @access public
  57. * @param string $prefix
  58. * @return string|void
  59. */
  60. public function prefix($prefix = '')
  61. {
  62. empty($this->init) && $this->boot();
  63. if (empty($prefix) && null !== $prefix) {
  64. return $this->prefix;
  65. } else {
  66. $this->prefix = $prefix;
  67. }
  68. }
  69. public static function __make(Config $config)
  70. {
  71. return new static($config->pull('session'));
  72. }
  73. /**
  74. * 配置
  75. * @access public
  76. * @param array $config
  77. * @return void
  78. */
  79. public function setConfig(array $config = [])
  80. {
  81. $this->config = array_merge($this->config, array_change_key_case($config));
  82. if (isset($config['prefix'])) {
  83. $this->prefix = $config['prefix'];
  84. }
  85. if (isset($config['use_lock'])) {
  86. $this->lock = $config['use_lock'];
  87. }
  88. }
  89. /**
  90. * session初始化
  91. * @access public
  92. * @param array $config
  93. * @return void
  94. * @throws \think\Exception
  95. */
  96. public function init(array $config = [])
  97. {
  98. $config = $config ?: $this->config;
  99. $isDoStart = false;
  100. if (isset($config['use_trans_sid'])) {
  101. ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0);
  102. }
  103. // 启动session
  104. if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) {
  105. ini_set('session.auto_start', 0);
  106. $isDoStart = true;
  107. }
  108. if (isset($config['prefix'])) {
  109. $this->prefix = $config['prefix'];
  110. }
  111. if (isset($config['use_lock'])) {
  112. $this->lock = $config['use_lock'];
  113. }
  114. if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) {
  115. session_id($_REQUEST[$config['var_session_id']]);
  116. } elseif (isset($config['id']) && !empty($config['id'])) {
  117. session_id($config['id']);
  118. }
  119. if (isset($config['name'])) {
  120. session_name($config['name']);
  121. }
  122. if (isset($config['path'])) {
  123. session_save_path($config['path']);
  124. }
  125. if (isset($config['domain'])) {
  126. ini_set('session.cookie_domain', $config['domain']);
  127. }
  128. if (isset($config['expire'])) {
  129. ini_set('session.gc_maxlifetime', $config['expire']);
  130. ini_set('session.cookie_lifetime', $config['expire']);
  131. }
  132. if (isset($config['secure'])) {
  133. ini_set('session.cookie_secure', $config['secure']);
  134. }
  135. if (isset($config['httponly'])) {
  136. ini_set('session.cookie_httponly', $config['httponly']);
  137. }
  138. if (isset($config['use_cookies'])) {
  139. ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0);
  140. }
  141. if (isset($config['cache_limiter'])) {
  142. session_cache_limiter($config['cache_limiter']);
  143. }
  144. if (isset($config['cache_expire'])) {
  145. session_cache_expire($config['cache_expire']);
  146. }
  147. if (!empty($config['type'])) {
  148. // 读取session驱动
  149. $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']);
  150. // 检查驱动类
  151. if (!class_exists($class) || !session_set_save_handler(new $class($config))) {
  152. throw new ClassNotFoundException('error session handler:' . $class, $class);
  153. }
  154. }
  155. if ($isDoStart) {
  156. session_start();
  157. $this->init = true;
  158. } else {
  159. $this->init = false;
  160. }
  161. return $this;
  162. }
  163. /**
  164. * session自动启动或者初始化
  165. * @access public
  166. * @return void
  167. */
  168. public function boot()
  169. {
  170. if (is_null($this->init)) {
  171. $this->init();
  172. }
  173. if (false === $this->init) {
  174. if (PHP_SESSION_ACTIVE != session_status()) {
  175. session_start();
  176. }
  177. $this->init = true;
  178. }
  179. }
  180. /**
  181. * session设置
  182. * @access public
  183. * @param string $name session名称
  184. * @param mixed $value session值
  185. * @param string|null $prefix 作用域(前缀)
  186. * @return void
  187. */
  188. public function set($name, $value, $prefix = null)
  189. {
  190. $this->lock();
  191. empty($this->init) && $this->boot();
  192. $prefix = !is_null($prefix) ? $prefix : $this->prefix;
  193. if (strpos($name, '.')) {
  194. // 二维数组赋值
  195. list($name1, $name2) = explode('.', $name);
  196. if ($prefix) {
  197. $_SESSION[$prefix][$name1][$name2] = $value;
  198. } else {
  199. $_SESSION[$name1][$name2] = $value;
  200. }
  201. } elseif ($prefix) {
  202. $_SESSION[$prefix][$name] = $value;
  203. } else {
  204. $_SESSION[$name] = $value;
  205. }
  206. $this->unlock();
  207. }
  208. /**
  209. * session获取
  210. * @access public
  211. * @param string $name session名称
  212. * @param string|null $prefix 作用域(前缀)
  213. * @return mixed
  214. */
  215. public function get($name = '', $prefix = null)
  216. {
  217. $this->lock();
  218. empty($this->init) && $this->boot();
  219. $prefix = !is_null($prefix) ? $prefix : $this->prefix;
  220. $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
  221. if ('' != $name) {
  222. $name = explode('.', $name);
  223. foreach ($name as $val) {
  224. if (isset($value[$val])) {
  225. $value = $value[$val];
  226. } else {
  227. $value = null;
  228. break;
  229. }
  230. }
  231. }
  232. $this->unlock();
  233. return $value;
  234. }
  235. /**
  236. * session 读写锁驱动实例化
  237. */
  238. protected function initDriver()
  239. {
  240. $config = $this->config;
  241. if (!empty($config['type']) && isset($config['use_lock']) && $config['use_lock']) {
  242. // 读取session驱动
  243. $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']);
  244. // 检查驱动类及类中是否存在 lock 和 unlock 函数
  245. if (class_exists($class) && method_exists($class, 'lock') && method_exists($class, 'unlock')) {
  246. $this->lockDriver = new $class($config);
  247. }
  248. }
  249. // 通过cookie获得session_id
  250. if (isset($config['name']) && $config['name']) {
  251. $this->sessKey = $config['name'];
  252. }
  253. if (isset($config['lock_timeout']) && $config['lock_timeout'] > 0) {
  254. $this->lockTimeout = $config['lock_timeout'];
  255. }
  256. }
  257. /**
  258. * session 读写加锁
  259. * @access protected
  260. * @return void
  261. */
  262. protected function lock()
  263. {
  264. if (empty($this->lock)) {
  265. return;
  266. }
  267. $this->initDriver();
  268. if (null !== $this->lockDriver && method_exists($this->lockDriver, 'lock')) {
  269. $t = time();
  270. // 使用 session_id 作为互斥条件,即只对同一 session_id 的会话互斥。第一次请求没有 session_id
  271. $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : '';
  272. do {
  273. if (time() - $t > $this->lockTimeout) {
  274. $this->unlock();
  275. }
  276. } while (!$this->lockDriver->lock($sessID, $this->lockTimeout));
  277. }
  278. }
  279. /**
  280. * session 读写解锁
  281. * @access protected
  282. * @return void
  283. */
  284. protected function unlock()
  285. {
  286. if (empty($this->lock)) {
  287. return;
  288. }
  289. $this->pause();
  290. if ($this->lockDriver && method_exists($this->lockDriver, 'unlock')) {
  291. $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : '';
  292. $this->lockDriver->unlock($sessID);
  293. }
  294. }
  295. /**
  296. * session获取并删除
  297. * @access public
  298. * @param string $name session名称
  299. * @param string|null $prefix 作用域(前缀)
  300. * @return mixed
  301. */
  302. public function pull($name, $prefix = null)
  303. {
  304. $result = $this->get($name, $prefix);
  305. if ($result) {
  306. $this->delete($name, $prefix);
  307. return $result;
  308. } else {
  309. return;
  310. }
  311. }
  312. /**
  313. * session设置 下一次请求有效
  314. * @access public
  315. * @param string $name session名称
  316. * @param mixed $value session值
  317. * @param string|null $prefix 作用域(前缀)
  318. * @return void
  319. */
  320. public function flash($name, $value)
  321. {
  322. $this->set($name, $value);
  323. if (!$this->has('__flash__.__time__')) {
  324. $this->set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']);
  325. }
  326. $this->push('__flash__', $name);
  327. }
  328. /**
  329. * 清空当前请求的session数据
  330. * @access public
  331. * @return void
  332. */
  333. public function flush()
  334. {
  335. if (!$this->init) {
  336. return;
  337. }
  338. $item = $this->get('__flash__');
  339. if (!empty($item)) {
  340. $time = $item['__time__'];
  341. if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) {
  342. unset($item['__time__']);
  343. $this->delete($item);
  344. $this->set('__flash__', []);
  345. }
  346. }
  347. }
  348. /**
  349. * 删除session数据
  350. * @access public
  351. * @param string|array $name session名称
  352. * @param string|null $prefix 作用域(前缀)
  353. * @return void
  354. */
  355. public function delete($name, $prefix = null)
  356. {
  357. empty($this->init) && $this->boot();
  358. $prefix = !is_null($prefix) ? $prefix : $this->prefix;
  359. if (is_array($name)) {
  360. foreach ($name as $key) {
  361. $this->delete($key, $prefix);
  362. }
  363. } elseif (strpos($name, '.')) {
  364. list($name1, $name2) = explode('.', $name);
  365. if ($prefix) {
  366. unset($_SESSION[$prefix][$name1][$name2]);
  367. } else {
  368. unset($_SESSION[$name1][$name2]);
  369. }
  370. } else {
  371. if ($prefix) {
  372. unset($_SESSION[$prefix][$name]);
  373. } else {
  374. unset($_SESSION[$name]);
  375. }
  376. }
  377. }
  378. /**
  379. * 清空session数据
  380. * @access public
  381. * @param string|null $prefix 作用域(前缀)
  382. * @return void
  383. */
  384. public function clear($prefix = null)
  385. {
  386. empty($this->init) && $this->boot();
  387. $prefix = !is_null($prefix) ? $prefix : $this->prefix;
  388. if ($prefix) {
  389. unset($_SESSION[$prefix]);
  390. } else {
  391. $_SESSION = [];
  392. }
  393. }
  394. /**
  395. * 判断session数据
  396. * @access public
  397. * @param string $name session名称
  398. * @param string|null $prefix
  399. * @return bool
  400. */
  401. public function has($name, $prefix = null)
  402. {
  403. empty($this->init) && $this->boot();
  404. $prefix = !is_null($prefix) ? $prefix : $this->prefix;
  405. if (strpos($name, '.')) {
  406. // 支持数组
  407. list($name1, $name2) = explode('.', $name);
  408. return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]);
  409. } else {
  410. return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]);
  411. }
  412. }
  413. /**
  414. * 添加数据到一个session数组
  415. * @access public
  416. * @param string $key
  417. * @param mixed $value
  418. * @return void
  419. */
  420. public function push($key, $value)
  421. {
  422. $array = $this->get($key);
  423. if (is_null($array)) {
  424. $array = [];
  425. }
  426. $array[] = $value;
  427. $this->set($key, $array);
  428. }
  429. /**
  430. * 启动session
  431. * @access public
  432. * @return void
  433. */
  434. public function start()
  435. {
  436. session_start();
  437. $this->init = true;
  438. }
  439. /**
  440. * 销毁session
  441. * @access public
  442. * @return void
  443. */
  444. public function destroy()
  445. {
  446. if (!empty($_SESSION)) {
  447. $_SESSION = [];
  448. }
  449. session_unset();
  450. session_destroy();
  451. $this->init = null;
  452. $this->lockDriver = null;
  453. }
  454. /**
  455. * 重新生成session_id
  456. * @access public
  457. * @param bool $delete 是否删除关联会话文件
  458. * @return void
  459. */
  460. public function regenerate($delete = false)
  461. {
  462. session_regenerate_id($delete);
  463. }
  464. /**
  465. * 暂停session
  466. * @access public
  467. * @return void
  468. */
  469. public function pause()
  470. {
  471. // 暂停session
  472. session_write_close();
  473. $this->init = false;
  474. }
  475. }