flzta commited on
Commit
673deff
·
verified ·
1 Parent(s): 9615b99

Upload 167 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. thinkphp/.gitignore +4 -0
  2. thinkphp/.htaccess +1 -0
  3. thinkphp/.travis.yml +47 -0
  4. thinkphp/CONTRIBUTING.md +119 -0
  5. thinkphp/LICENSE.txt +32 -0
  6. thinkphp/README.md +114 -0
  7. thinkphp/base.php +65 -0
  8. thinkphp/codecov.yml +12 -0
  9. thinkphp/composer.json +35 -0
  10. thinkphp/console.php +20 -0
  11. thinkphp/convention.php +298 -0
  12. thinkphp/helper.php +589 -0
  13. thinkphp/lang/zh-cn.php +137 -0
  14. thinkphp/library/think/App.php +677 -0
  15. thinkphp/library/think/Build.php +235 -0
  16. thinkphp/library/think/Cache.php +251 -0
  17. thinkphp/library/think/Collection.php +467 -0
  18. thinkphp/library/think/Config.php +214 -0
  19. thinkphp/library/think/Console.php +863 -0
  20. thinkphp/library/think/Controller.php +239 -0
  21. thinkphp/library/think/Cookie.php +268 -0
  22. thinkphp/library/think/Db.php +180 -0
  23. thinkphp/library/think/Debug.php +252 -0
  24. thinkphp/library/think/Env.php +39 -0
  25. thinkphp/library/think/Error.php +136 -0
  26. thinkphp/library/think/Exception.php +55 -0
  27. thinkphp/library/think/File.php +478 -0
  28. thinkphp/library/think/Hook.php +148 -0
  29. thinkphp/library/think/Lang.php +265 -0
  30. thinkphp/library/think/Loader.php +677 -0
  31. thinkphp/library/think/Log.php +237 -0
  32. thinkphp/library/think/Model.php +2350 -0
  33. thinkphp/library/think/Paginator.php +409 -0
  34. thinkphp/library/think/Process.php +1205 -0
  35. thinkphp/library/think/Request.php +1690 -0
  36. thinkphp/library/think/Response.php +332 -0
  37. thinkphp/library/think/Route.php +1645 -0
  38. thinkphp/library/think/Session.php +366 -0
  39. thinkphp/library/think/Template.php +1139 -0
  40. thinkphp/library/think/Url.php +333 -0
  41. thinkphp/library/think/Validate.php +1371 -0
  42. thinkphp/library/think/View.php +253 -0
  43. thinkphp/library/think/cache/Driver.php +231 -0
  44. thinkphp/library/think/cache/driver/File.php +268 -0
  45. thinkphp/library/think/cache/driver/Lite.php +187 -0
  46. thinkphp/library/think/cache/driver/Memcache.php +177 -0
  47. thinkphp/library/think/cache/driver/Memcached.php +187 -0
  48. thinkphp/library/think/cache/driver/Redis.php +188 -0
  49. thinkphp/library/think/cache/driver/Sqlite.php +199 -0
  50. thinkphp/library/think/cache/driver/Wincache.php +152 -0
thinkphp/.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ /composer.lock
2
+ /vendor
3
+ .idea
4
+ .DS_Store
thinkphp/.htaccess ADDED
@@ -0,0 +1 @@
 
 
1
+ deny from all
thinkphp/.travis.yml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ sudo: false
2
+
3
+ language: php
4
+
5
+ services:
6
+ - memcached
7
+ - mongodb
8
+ - mysql
9
+ - postgresql
10
+ - redis-server
11
+
12
+ matrix:
13
+ fast_finish: true
14
+ include:
15
+ - php: 5.4
16
+ - php: 5.5
17
+ - php: 5.6
18
+ - php: 7.0
19
+ - php: hhvm
20
+ allow_failures:
21
+ - php: hhvm
22
+
23
+ cache:
24
+ directories:
25
+ - $HOME/.composer/cache
26
+
27
+ before_install:
28
+ - composer self-update
29
+ - mysql -e "create database IF NOT EXISTS test;" -uroot
30
+ - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres
31
+ - psql -c 'create database test;' -U postgres
32
+
33
+ install:
34
+ - ./tests/script/install.sh
35
+
36
+ script:
37
+ ## LINT
38
+ - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \;
39
+ ## PHP Copy/Paste Detector
40
+ - vendor/bin/phpcpd --verbose --exclude vendor ./ || true
41
+ ## PHPLOC
42
+ - vendor/bin/phploc --exclude vendor ./
43
+ ## PHPUNIT
44
+ - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml
45
+
46
+ after_success:
47
+ - bash <(curl -s https://codecov.io/bash)
thinkphp/CONTRIBUTING.md ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 如何贡献我的源代码
2
+ ===
3
+
4
+ 此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。
5
+
6
+ ## 通过 Github 贡献代码
7
+
8
+ ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。
9
+
10
+ 参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。
11
+
12
+ 我们希望你贡献的代码符合:
13
+
14
+ * ThinkPHP 的编码规范
15
+ * 适当的注释,能让其他人读懂
16
+ * 遵循 Apache2 开源协议
17
+
18
+ **如果想要了解更多细节或有任何疑问,请继续阅读下面的内容**
19
+
20
+ ### 注意事项
21
+
22
+ * 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141);
23
+ * 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144);
24
+ * 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。
25
+ * 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范;
26
+ * 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests);
27
+
28
+ ## GitHub Issue
29
+
30
+ GitHub 提供了 Issue 功能,该功能可以用于:
31
+
32
+ * 提出 bug
33
+ * 提出功能改进
34
+ * 反馈使用体验
35
+
36
+ 该功能不应该用于:
37
+
38
+ * 提出修改意见(涉及代码署名和修订追溯问题)
39
+ * 不友善的言论
40
+
41
+ ## 快速修改
42
+
43
+ **GitHub 提供了快速编辑文件的功能**
44
+
45
+ 1. 登录 GitHub 帐号;
46
+ 2. 浏览项目文件,找到要进行修改的文件;
47
+ 3. 点击右上角铅笔图标进行修改;
48
+ 4. 填写 `Commit changes` 相关内容(Title 必填);
49
+ 5. 提交修改,等待 CI 验证和管理员合并。
50
+
51
+ **若您需要一次提交大量修改,请继续阅读下面的内容**
52
+
53
+ ## 完整流程
54
+
55
+ 1. `fork`本项目;
56
+ 2. 克隆(`clone`)你 `fork` 的项目到本地;
57
+ 3. 新建分支(`branch`)并检出(`checkout`)新分支;
58
+ 4. 添加本项目到你的本地 git 仓库作为上游(`upstream`);
59
+ 5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests);
60
+ 6. 变基(衍合 `rebase`)你的分支到上游 master 分支;
61
+ 7. `push` 你的本地仓库到 GitHub;
62
+ 8. 提交 `pull request`;
63
+ 9. 等待 CI 验证(若不通过则重复 5~7,不需要重新提交 `pull request`,GitHub 会自动更新你的 `pull request`);
64
+ 10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。
65
+
66
+ *若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`*
67
+
68
+ *绝对不可以使用 `git push -f` 强行推送修改到上游*
69
+
70
+ ### 注意事项
71
+
72
+ * 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/);
73
+ * 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分);
74
+ * 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/)
75
+
76
+ ## 推荐资源
77
+
78
+ ### 开发环境
79
+
80
+ * XAMPP for Windows 5.5.x
81
+ * WampServer (for Windows)
82
+ * upupw Apache PHP5.4 ( for Windows)
83
+
84
+ 或自行安装
85
+
86
+ - Apache / Nginx
87
+ - PHP 5.4 ~ 5.6
88
+ - MySQL / MariaDB
89
+
90
+ *Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer*
91
+
92
+ *Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB*
93
+
94
+ ### 编辑器
95
+
96
+ Sublime Text 3 + phpfmt 插件
97
+
98
+ phpfmt 插件参数
99
+
100
+ ```json
101
+ {
102
+ "autocomplete": true,
103
+ "enable_auto_align": true,
104
+ "format_on_save": true,
105
+ "indent_with_space": true,
106
+ "psr1_naming": false,
107
+ "psr2": true,
108
+ "version": 4
109
+ }
110
+ ```
111
+
112
+ 或其他 编辑器 / IDE 配合 PSR2 自动格式化工具
113
+
114
+ ### Git GUI
115
+
116
+ * SourceTree
117
+ * GitHub Desktop
118
+
119
+ 或其他 Git 图形界面客户端
thinkphp/LICENSE.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
3
+ 版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn)
4
+ All rights reserved。
5
+ ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
6
+
7
+ Apache Licence是著名的非盈利开源组织Apache采用的协议。
8
+ 该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
9
+ 允许代码修改,再作为开源或商业软件发布。需要满足
10
+ 的条件:
11
+ 1. 需要给代码的用户一份Apache Licence ;
12
+ 2. 如果你修改了代码,需要在被修改的文件中说明;
13
+ 3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
14
+ 带有原来代码中的协议,商标,专利声明和其他原来作者规
15
+ 定需要包含的说明;
16
+ 4. 如果再发布的产品中包含一个Notice文件,则在Notice文
17
+ 件中需要带有本协议内容。你可以在Notice中增加自己的
18
+ 许可,但不可以表现为对Apache Licence构成更改。
19
+ 具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25
+ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ POSSIBILITY OF SUCH DAMAGE.
thinkphp/README.md ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ThinkPHP 5.0
2
+ ===============
3
+
4
+ [![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411)
5
+ [![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework)
6
+ [![codecov.io](http://codecov.io/github/top-think/framework/coverage.svg?branch=master)](http://codecov.io/github/github/top-think/framework?branch=master)
7
+ [![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework)
8
+ [![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework)
9
+ [![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework)
10
+ [![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework)
11
+
12
+ ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括:
13
+
14
+ + 基于命名空间和众多PHP新特性
15
+ + 核心功能组件化
16
+ + 强化路由功能
17
+ + 更灵活的控制器
18
+ + 重构的模型和数据库类
19
+ + 配置文件可分离
20
+ + 重写的自动验证和完成
21
+ + 简化扩展机制
22
+ + API支持完善
23
+ + 改进的Log类
24
+ + 命令行访问支持
25
+ + REST支持
26
+ + 引导文件支持
27
+ + 方便的自动生成定义
28
+ + 真正惰性加载
29
+ + 分布式环境支持
30
+ + 支持Composer
31
+ + 支持MongoDb
32
+
33
+ > ThinkPHP5的运行环境要求PHP5.4以上。
34
+
35
+ 详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart)
36
+
37
+ ## 目录结构
38
+
39
+ 初始的目录结构如下:
40
+
41
+ ~~~
42
+ www WEB部署目录(或者子目录)
43
+ ├─application 应用目录
44
+ │ ├─common 公共模块目录(可以更改)
45
+ │ ├─module_name 模块目录
46
+ │ │ ├─config.php 模块配置文件
47
+ │ │ ├─common.php 模块函数文件
48
+ │ │ ├─controller 控制器目录
49
+ │ │ ├─model 模型目录
50
+ │ │ ├─view 视图目录
51
+ │ │ └─ ... 更多类库目录
52
+ │ │
53
+ │ ├─command.php 命令行工具配置文件
54
+ │ ├─common.php 公共函数文件
55
+ │ ├─config.php 公共配置文件
56
+ │ ├─route.php 路由配置文件
57
+ │ ├─tags.php 应用行为扩展定义文件
58
+ │ └─database.php 数据库配置文件
59
+
60
+ ├─public WEB目录(对外访问目录)
61
+ │ ├─index.php 入口文件
62
+ │ ├─router.php 快速测试文件
63
+ │ └─.htaccess 用于apache的重写
64
+
65
+ ├─thinkphp 框架系统目录
66
+ │ ├─lang 语言文件目录
67
+ │ ├─library 框架类库目录
68
+ │ │ ├─think Think类库包目录
69
+ │ │ └─traits 系统Trait目录
70
+ │ │
71
+ │ ├─tpl 系统模板目录
72
+ │ ├─base.php 基础定义文件
73
+ │ ├─console.php 控制台入口文件
74
+ │ ├─convention.php 框架惯例配置文件
75
+ │ ├─helper.php 助手函数文件
76
+ │ ├─phpunit.xml phpunit配置文件
77
+ │ └─start.php 框架入口文件
78
+
79
+ ├─extend 扩展类库目录
80
+ ├─runtime 应用的运行时目录(可写,可定制)
81
+ ├─vendor 第三方类库目录(Composer依赖库)
82
+ ├─build.php 自动生成定义文件(参考)
83
+ ├─composer.json composer 定义文件
84
+ ├─LICENSE.txt 授权说明文件
85
+ ├─README.md README 文件
86
+ ├─think 命令行入口文件
87
+ ~~~
88
+
89
+ > router.php用于php自带webserver支持,可用于快速测试
90
+ > 切换到public目录后,启动命令:php -S localhost:8888 router.php
91
+ > 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。
92
+
93
+ ## 命名规范
94
+
95
+ ThinkPHP5的命名规范遵循`PSR-2`规范以及`PSR-4`自动加载规范。
96
+
97
+ ## 参与开发
98
+ 注册并登录 Github 帐号, fork 本项目并进行改动。
99
+
100
+ 更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md)
101
+
102
+ ## 版权信息
103
+
104
+ ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
105
+
106
+ 本项目包含的第三方源码和二进制文件之版权信息另行标注。
107
+
108
+ 版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
109
+
110
+ All rights reserved。
111
+
112
+ ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
113
+
114
+ 更多细节参阅 [LICENSE.txt](LICENSE.txt)
thinkphp/base.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ define('THINK_VERSION', '5.0.24');
13
+ define('THINK_START_TIME', microtime(true));
14
+ define('THINK_START_MEM', memory_get_usage());
15
+ define('EXT', '.php');
16
+ define('DS', DIRECTORY_SEPARATOR);
17
+ defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS);
18
+ define('LIB_PATH', THINK_PATH . 'library' . DS);
19
+ define('CORE_PATH', LIB_PATH . 'think' . DS);
20
+ define('TRAIT_PATH', LIB_PATH . 'traits' . DS);
21
+ defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS);
22
+ defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS);
23
+ defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS);
24
+ defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS);
25
+ defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS);
26
+ defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS);
27
+ defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS);
28
+ defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS);
29
+ defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录
30
+ defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀
31
+ defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀
32
+
33
+ // 环境常量
34
+ define('IS_CLI', PHP_SAPI == 'cli' ? true : false);
35
+ define('IS_WIN', strpos(PHP_OS, 'WIN') !== false);
36
+
37
+ // 载入Loader类
38
+ require CORE_PATH . 'Loader.php';
39
+
40
+ // 加载环境变量配置文件
41
+ if (is_file(ROOT_PATH . '.env')) {
42
+ $env = parse_ini_file(ROOT_PATH . '.env', true);
43
+
44
+ foreach ($env as $key => $val) {
45
+ $name = ENV_PREFIX . strtoupper($key);
46
+
47
+ if (is_array($val)) {
48
+ foreach ($val as $k => $v) {
49
+ $item = $name . '_' . strtoupper($k);
50
+ putenv("$item=$v");
51
+ }
52
+ } else {
53
+ putenv("$name=$val");
54
+ }
55
+ }
56
+ }
57
+
58
+ // 注册自动加载
59
+ \think\Loader::register();
60
+
61
+ // 注册错误和异常处理机制
62
+ \think\Error::register();
63
+
64
+ // 加载惯例配置文件
65
+ \think\Config::set(include THINK_PATH . 'convention' . EXT);
thinkphp/codecov.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ comment:
2
+ layout: header, changes, diff
3
+ coverage:
4
+ ignore:
5
+ - base.php
6
+ - helper.php
7
+ - convention.php
8
+ - lang/zh-cn.php
9
+ - start.php
10
+ - console.php
11
+ status:
12
+ patch: false
thinkphp/composer.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "topthink/framework",
3
+ "description": "the new thinkphp framework",
4
+ "type": "think-framework",
5
+ "keywords": [
6
+ "framework",
7
+ "thinkphp",
8
+ "ORM"
9
+ ],
10
+ "homepage": "http://thinkphp.cn/",
11
+ "license": "Apache-2.0",
12
+ "authors": [
13
+ {
14
+ "name": "liu21st",
15
+ "email": "liu21st@gmail.com"
16
+ }
17
+ ],
18
+ "require": {
19
+ "php": ">=5.4.0",
20
+ "topthink/think-installer": "~1.0"
21
+ },
22
+ "require-dev": {
23
+ "phpunit/phpunit": "4.8.*",
24
+ "johnkary/phpunit-speedtrap": "^1.0",
25
+ "mikey179/vfsStream": "~1.6",
26
+ "phploc/phploc": "2.*",
27
+ "sebastian/phpcpd": "2.*",
28
+ "phpdocumentor/reflection-docblock": "^2.0"
29
+ },
30
+ "autoload": {
31
+ "psr-4": {
32
+ "think\\": "library/think"
33
+ }
34
+ }
35
+ }
thinkphp/console.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // +----------------------------------------------------------------------
3
+ // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
4
+ // +----------------------------------------------------------------------
5
+ // | Copyright (c) 2006-2017 http://thinkphp.cn All rights reserved.
6
+ // +----------------------------------------------------------------------
7
+ // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+ // +----------------------------------------------------------------------
9
+ // | Author: yunwuxin <448901948@qq.com>
10
+ // +----------------------------------------------------------------------
11
+
12
+ namespace think;
13
+
14
+ // ThinkPHP 引导文件
15
+ // 加载基础文件
16
+ require __DIR__ . '/base.php';
17
+
18
+ // 执行应用
19
+ App::initCommon();
20
+ Console::init();
thinkphp/convention.php ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ return [
4
+ // +----------------------------------------------------------------------
5
+ // | 应用设置
6
+ // +----------------------------------------------------------------------
7
+ // 默认Host地址
8
+ 'app_host' => '',
9
+ // 应用调试模式
10
+ 'app_debug' => false,
11
+ // 应用Trace
12
+ 'app_trace' => false,
13
+ // 应用模式状态
14
+ 'app_status' => '',
15
+ // 是否支持多模块
16
+ 'app_multi_module' => true,
17
+ // 入口自动绑定模块
18
+ 'auto_bind_module' => false,
19
+ // 注册的根命名空间
20
+ 'root_namespace' => [],
21
+ // 扩展函数文件
22
+ 'extra_file_list' => [THINK_PATH . 'helper' . EXT],
23
+ // 默认输出类型
24
+ 'default_return_type' => 'html',
25
+ // 默认AJAX 数据返回格式,可选json xml ...
26
+ 'default_ajax_return' => 'json',
27
+ // 默认JSONP格式返回的处理方法
28
+ 'default_jsonp_handler' => 'jsonpReturn',
29
+ // 默认JSONP处理方法
30
+ 'var_jsonp_handler' => 'callback',
31
+ // 默认时区
32
+ 'default_timezone' => 'PRC',
33
+ // 是否开启多语言
34
+ 'lang_switch_on' => false,
35
+ // 默认全局过滤方法 用逗号分隔多个
36
+ 'default_filter' => '',
37
+ // 默认语言
38
+ 'default_lang' => 'zh-cn',
39
+ // 应用类库后缀
40
+ 'class_suffix' => false,
41
+ // 控制器类后缀
42
+ 'controller_suffix' => false,
43
+
44
+ // +----------------------------------------------------------------------
45
+ // | 模块设置
46
+ // +----------------------------------------------------------------------
47
+
48
+ // 默认模块名
49
+ 'default_module' => 'index',
50
+ // 禁止访问模块
51
+ 'deny_module_list' => ['common'],
52
+ // 默认控制器名
53
+ 'default_controller' => 'Index',
54
+ // 默认操作名
55
+ 'default_action' => 'index',
56
+ // 默认验证器
57
+ 'default_validate' => '',
58
+ // 默认的空控制器名
59
+ 'empty_controller' => 'Error',
60
+ // 操作方法前缀
61
+ 'use_action_prefix' => false,
62
+ // 操作方法后缀
63
+ 'action_suffix' => '',
64
+ // 自动搜索控制器
65
+ 'controller_auto_search' => false,
66
+
67
+ // +----------------------------------------------------------------------
68
+ // | URL设置
69
+ // +----------------------------------------------------------------------
70
+
71
+ // PATHINFO变量名 用于兼容模式
72
+ 'var_pathinfo' => 's',
73
+ // 兼容PATH_INFO获取
74
+ 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
75
+ // pathinfo分隔符
76
+ 'pathinfo_depr' => '/',
77
+ // HTTPS代理标识
78
+ 'https_agent_name' => '',
79
+ // URL伪静态后缀
80
+ 'url_html_suffix' => 'html',
81
+ // URL普通方式参数 用于自动生成
82
+ 'url_common_param' => false,
83
+ // URL参数方式 0 按名称成对解析 1 按顺序解析
84
+ 'url_param_type' => 0,
85
+ // 是否开启路由
86
+ 'url_route_on' => true,
87
+ // 路由配置文件(支持配置多个)
88
+ 'route_config_file' => ['route'],
89
+ // 路由使用完整匹配
90
+ 'route_complete_match' => false,
91
+ // 是否强制使用路由
92
+ 'url_route_must' => false,
93
+ // 域名部署
94
+ 'url_domain_deploy' => false,
95
+ // 域名根,如thinkphp.cn
96
+ 'url_domain_root' => '',
97
+ // 是否自动转换URL中的控制器和操作名
98
+ 'url_convert' => true,
99
+ // 默认的访问控制器层
100
+ 'url_controller_layer' => 'controller',
101
+ // 表单请求类型伪装变量
102
+ 'var_method' => '_method',
103
+ // 表单ajax伪装变量
104
+ 'var_ajax' => '_ajax',
105
+ // 表单pjax伪装变量
106
+ 'var_pjax' => '_pjax',
107
+ // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
108
+ 'request_cache' => false,
109
+ // 请求缓存有效期
110
+ 'request_cache_expire' => null,
111
+ // 全局请求缓存排除规则
112
+ 'request_cache_except' => [],
113
+
114
+ // +----------------------------------------------------------------------
115
+ // | 模板设置
116
+ // +----------------------------------------------------------------------
117
+
118
+ 'template' => [
119
+ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
120
+ 'auto_rule' => 1,
121
+ // 模板引擎类型 支持 php think 支持扩展
122
+ 'type' => 'Think',
123
+ // 视图基础目录,配置目录为所有模块的视图起始目录
124
+ 'view_base' => '',
125
+ // 当前模板的视图目录 留空为自动获取
126
+ 'view_path' => '',
127
+ // 模板后缀
128
+ 'view_suffix' => 'html',
129
+ // 模板文件名分隔符
130
+ 'view_depr' => DS,
131
+ // 模板引擎普通标签开始标记
132
+ 'tpl_begin' => '{',
133
+ // 模板引擎普通标签结束标记
134
+ 'tpl_end' => '}',
135
+ // 标签库标签开始标记
136
+ 'taglib_begin' => '{',
137
+ // 标签库标签结束标记
138
+ 'taglib_end' => '}',
139
+ ],
140
+
141
+ // 视图输出字符串内容替换
142
+ 'view_replace_str' => [],
143
+ // 默认跳转页面对应的模板文件
144
+ 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
145
+ 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
146
+
147
+ // +----------------------------------------------------------------------
148
+ // | 异常及错误设置
149
+ // +----------------------------------------------------------------------
150
+
151
+ // 异常页面的模板文件
152
+ 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl',
153
+
154
+ // 错误显示信息,非调试模式有效
155
+ 'error_message' => '页面错误!请稍后再试~',
156
+ // 显示错误信息
157
+ 'show_error_msg' => false,
158
+ // 异常处理handle类 留空使用 \think\exception\Handle
159
+ 'exception_handle' => '',
160
+ // 是否记录trace信息到日志
161
+ 'record_trace' => false,
162
+
163
+ // +----------------------------------------------------------------------
164
+ // | 日志设置
165
+ // +----------------------------------------------------------------------
166
+
167
+ 'log' => [
168
+ // 日志记录方式,内置 file socket 支持扩展
169
+ 'type' => 'File',
170
+ // 日志保存目录
171
+ 'path' => LOG_PATH,
172
+ // 日志记录级别
173
+ 'level' => [],
174
+ ],
175
+
176
+ // +----------------------------------------------------------------------
177
+ // | Trace设置 开启 app_trace 后 有效
178
+ // +----------------------------------------------------------------------
179
+ 'trace' => [
180
+ // 内置Html Console 支持扩展
181
+ 'type' => 'Html',
182
+ ],
183
+
184
+ // +----------------------------------------------------------------------
185
+ // | 缓存设置
186
+ // +----------------------------------------------------------------------
187
+
188
+ 'cache' => [
189
+ // 驱动方式
190
+ 'type' => 'File',
191
+ // 缓存保存目录
192
+ 'path' => CACHE_PATH,
193
+ // 缓存前缀
194
+ 'prefix' => '',
195
+ // 缓存有效期 0表示永久缓存
196
+ 'expire' => 0,
197
+ ],
198
+
199
+ // +----------------------------------------------------------------------
200
+ // | 会话设置
201
+ // +----------------------------------------------------------------------
202
+
203
+ 'session' => [
204
+ 'id' => '',
205
+ // SESSION_ID的提交变量,解决flash上传跨域
206
+ 'var_session_id' => '',
207
+ // SESSION 前缀
208
+ 'prefix' => 'think',
209
+ // 驱动方式 支持redis memcache memcached
210
+ 'type' => '',
211
+ // 是否自动开启 SESSION
212
+ 'auto_start' => true,
213
+ 'httponly' => true,
214
+ 'secure' => false,
215
+ ],
216
+
217
+ // +----------------------------------------------------------------------
218
+ // | Cookie设置
219
+ // +----------------------------------------------------------------------
220
+ 'cookie' => [
221
+ // cookie 名称前缀
222
+ 'prefix' => '',
223
+ // cookie 保存时间
224
+ 'expire' => 0,
225
+ // cookie 保存路径
226
+ 'path' => '/',
227
+ // cookie 有效域名
228
+ 'domain' => '',
229
+ // cookie 启用安全传输
230
+ 'secure' => false,
231
+ // httponly设置
232
+ 'httponly' => '',
233
+ // 是否使用 setcookie
234
+ 'setcookie' => true,
235
+ ],
236
+
237
+ // +----------------------------------------------------------------------
238
+ // | 数据库设置
239
+ // +----------------------------------------------------------------------
240
+
241
+ 'database' => [
242
+ // 数据库类型
243
+ 'type' => 'mysql',
244
+ // 数据库连接DSN配置
245
+ 'dsn' => '',
246
+ // 服务器地址
247
+ 'hostname' => '127.0.0.1',
248
+ // 数据库名
249
+ 'database' => '',
250
+ // 数据库用户名
251
+ 'username' => 'root',
252
+ // 数据库密码
253
+ 'password' => '',
254
+ // 数据库连接端口
255
+ 'hostport' => '',
256
+ // 数据库连接参数
257
+ 'params' => [],
258
+ // 数据库编码默认采用utf8
259
+ 'charset' => 'utf8',
260
+ // 数据库表前缀
261
+ 'prefix' => '',
262
+ // 数据库调试模式
263
+ 'debug' => false,
264
+ // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
265
+ 'deploy' => 0,
266
+ // 数据库读写是否分离 主从式有效
267
+ 'rw_separate' => false,
268
+ // 读写分离后 主服务器数量
269
+ 'master_num' => 1,
270
+ // 指定从服务器序号
271
+ 'slave_no' => '',
272
+ // 是否严格检查字段是否存在
273
+ 'fields_strict' => true,
274
+ // 数据集返回类型
275
+ 'resultset_type' => 'array',
276
+ // 自动写入时间戳字段
277
+ 'auto_timestamp' => false,
278
+ // 时间字段取出后的默认时间格式
279
+ 'datetime_format' => 'Y-m-d H:i:s',
280
+ // 是否需要进行SQL性能分析
281
+ 'sql_explain' => false,
282
+ ],
283
+
284
+ //分页配置
285
+ 'paginate' => [
286
+ 'type' => 'bootstrap',
287
+ 'var_page' => 'page',
288
+ 'list_rows' => 15,
289
+ ],
290
+
291
+ //控制台配置
292
+ 'console' => [
293
+ 'name' => 'Think Console',
294
+ 'version' => '0.1',
295
+ 'user' => null,
296
+ ],
297
+
298
+ ];
thinkphp/helper.php ADDED
@@ -0,0 +1,589 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ //------------------------
13
+ // ThinkPHP 助手函数
14
+ //-------------------------
15
+
16
+ use think\Cache;
17
+ use think\Config;
18
+ use think\Cookie;
19
+ use think\Db;
20
+ use think\Debug;
21
+ use think\exception\HttpException;
22
+ use think\exception\HttpResponseException;
23
+ use think\Lang;
24
+ use think\Loader;
25
+ use think\Log;
26
+ use think\Model;
27
+ use think\Request;
28
+ use think\Response;
29
+ use think\Session;
30
+ use think\Url;
31
+ use think\View;
32
+
33
+ if (!function_exists('load_trait')) {
34
+ /**
35
+ * 快速导入Traits PHP5.5以上无需调用
36
+ * @param string $class trait库
37
+ * @param string $ext 类库后缀
38
+ * @return boolean
39
+ */
40
+ function load_trait($class, $ext = EXT)
41
+ {
42
+ return Loader::import($class, TRAIT_PATH, $ext);
43
+ }
44
+ }
45
+
46
+ if (!function_exists('exception')) {
47
+ /**
48
+ * 抛出异常处理
49
+ *
50
+ * @param string $msg 异常消息
51
+ * @param integer $code 异常代码 默认为0
52
+ * @param string $exception 异常类
53
+ *
54
+ * @throws Exception
55
+ */
56
+ function exception($msg, $code = 0, $exception = '')
57
+ {
58
+ $e = $exception ?: '\think\Exception';
59
+ throw new $e($msg, $code);
60
+ }
61
+ }
62
+
63
+ if (!function_exists('debug')) {
64
+ /**
65
+ * 记录时间(微秒)和内存使用情况
66
+ * @param string $start 开始标签
67
+ * @param string $end 结束标签
68
+ * @param integer|string $dec 小数位 如果是m 表示统计内存占用
69
+ * @return mixed
70
+ */
71
+ function debug($start, $end = '', $dec = 6)
72
+ {
73
+ if ('' == $end) {
74
+ Debug::remark($start);
75
+ } else {
76
+ return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec);
77
+ }
78
+ }
79
+ }
80
+
81
+ if (!function_exists('lang')) {
82
+ /**
83
+ * 获取语言变量值
84
+ * @param string $name 语言变量名
85
+ * @param array $vars 动态变量值
86
+ * @param string $lang 语言
87
+ * @return mixed
88
+ */
89
+ function lang($name, $vars = [], $lang = '')
90
+ {
91
+ return Lang::get($name, $vars, $lang);
92
+ }
93
+ }
94
+
95
+ if (!function_exists('config')) {
96
+ /**
97
+ * 获取和设置配置参数
98
+ * @param string|array $name 参数名
99
+ * @param mixed $value 参数值
100
+ * @param string $range 作用域
101
+ * @return mixed
102
+ */
103
+ function config($name = '', $value = null, $range = '')
104
+ {
105
+ if (is_null($value) && is_string($name)) {
106
+ return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range);
107
+ } else {
108
+ return Config::set($name, $value, $range);
109
+ }
110
+ }
111
+ }
112
+
113
+ if (!function_exists('input')) {
114
+ /**
115
+ * 获取输入数据 支持默认值和过滤
116
+ * @param string $key 获取的变量名
117
+ * @param mixed $default 默认值
118
+ * @param string $filter 过滤方法
119
+ * @return mixed
120
+ */
121
+ function input($key = '', $default = null, $filter = '')
122
+ {
123
+ if (0 === strpos($key, '?')) {
124
+ $key = substr($key, 1);
125
+ $has = true;
126
+ }
127
+ if ($pos = strpos($key, '.')) {
128
+ // 指定参数来源
129
+ list($method, $key) = explode('.', $key, 2);
130
+ if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
131
+ $key = $method . '.' . $key;
132
+ $method = 'param';
133
+ }
134
+ } else {
135
+ // 默认为自动判断
136
+ $method = 'param';
137
+ }
138
+ if (isset($has)) {
139
+ return request()->has($key, $method, $default);
140
+ } else {
141
+ return request()->$method($key, $default, $filter);
142
+ }
143
+ }
144
+ }
145
+
146
+ if (!function_exists('widget')) {
147
+ /**
148
+ * 渲染输出Widget
149
+ * @param string $name Widget名称
150
+ * @param array $data 传入的参数
151
+ * @return mixed
152
+ */
153
+ function widget($name, $data = [])
154
+ {
155
+ return Loader::action($name, $data, 'widget');
156
+ }
157
+ }
158
+
159
+ if (!function_exists('model')) {
160
+ /**
161
+ * 实例化Model
162
+ * @param string $name Model名称
163
+ * @param string $layer 业务层名称
164
+ * @param bool $appendSuffix 是否添加类名后缀
165
+ * @return \think\Model
166
+ */
167
+ function model($name = '', $layer = 'model', $appendSuffix = false)
168
+ {
169
+ return Loader::model($name, $layer, $appendSuffix);
170
+ }
171
+ }
172
+
173
+ if (!function_exists('validate')) {
174
+ /**
175
+ * 实例化验证器
176
+ * @param string $name 验证器名称
177
+ * @param string $layer 业务层名称
178
+ * @param bool $appendSuffix 是否添加类名后缀
179
+ * @return \think\Validate
180
+ */
181
+ function validate($name = '', $layer = 'validate', $appendSuffix = false)
182
+ {
183
+ return Loader::validate($name, $layer, $appendSuffix);
184
+ }
185
+ }
186
+
187
+ if (!function_exists('db')) {
188
+ /**
189
+ * 实例化数据库类
190
+ * @param string $name 操作的数据表名称(不含前缀)
191
+ * @param array|string $config 数据库配置参数
192
+ * @param bool $force 是否强制重新连接
193
+ * @return \think\db\Query
194
+ */
195
+ function db($name = '', $config = [], $force = false)
196
+ {
197
+ return Db::connect($config, $force)->name($name);
198
+ }
199
+ }
200
+
201
+ if (!function_exists('controller')) {
202
+ /**
203
+ * 实例化控制器 格式:[模块/]控制器
204
+ * @param string $name 资源地址
205
+ * @param string $layer 控制层名称
206
+ * @param bool $appendSuffix 是否添加类名后缀
207
+ * @return \think\Controller
208
+ */
209
+ function controller($name, $layer = 'controller', $appendSuffix = false)
210
+ {
211
+ return Loader::controller($name, $layer, $appendSuffix);
212
+ }
213
+ }
214
+
215
+ if (!function_exists('action')) {
216
+ /**
217
+ * 调用模块的操作方法 参数格式 [模块/控制器/]操作
218
+ * @param string $url 调用地址
219
+ * @param string|array $vars 调用参数 支持字符串和数组
220
+ * @param string $layer 要调用的控制层名称
221
+ * @param bool $appendSuffix 是否添加类名后缀
222
+ * @return mixed
223
+ */
224
+ function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
225
+ {
226
+ return Loader::action($url, $vars, $layer, $appendSuffix);
227
+ }
228
+ }
229
+
230
+ if (!function_exists('import')) {
231
+ /**
232
+ * 导入所需的类库 同java的Import 本函数有缓存功能
233
+ * @param string $class 类库命名空间字符串
234
+ * @param string $baseUrl 起始路径
235
+ * @param string $ext 导入的文件扩展名
236
+ * @return boolean
237
+ */
238
+ function import($class, $baseUrl = '', $ext = EXT)
239
+ {
240
+ return Loader::import($class, $baseUrl, $ext);
241
+ }
242
+ }
243
+
244
+ if (!function_exists('vendor')) {
245
+ /**
246
+ * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面
247
+ * @param string $class 类库
248
+ * @param string $ext 类库后缀
249
+ * @return boolean
250
+ */
251
+ function vendor($class, $ext = EXT)
252
+ {
253
+ return Loader::import($class, VENDOR_PATH, $ext);
254
+ }
255
+ }
256
+
257
+ if (!function_exists('dump')) {
258
+ /**
259
+ * 浏览器友好的变量输出
260
+ * @param mixed $var 变量
261
+ * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串
262
+ * @param string $label 标签 默认为空
263
+ * @return void|string
264
+ */
265
+ function dump($var, $echo = true, $label = null)
266
+ {
267
+ return Debug::dump($var, $echo, $label);
268
+ }
269
+ }
270
+
271
+ if (!function_exists('url')) {
272
+ /**
273
+ * Url生成
274
+ * @param string $url 路由地址
275
+ * @param string|array $vars 变量
276
+ * @param bool|string $suffix 生成的URL后缀
277
+ * @param bool|string $domain 域名
278
+ * @return string
279
+ */
280
+ function url($url = '', $vars = '', $suffix = true, $domain = false)
281
+ {
282
+ return Url::build($url, $vars, $suffix, $domain);
283
+ }
284
+ }
285
+
286
+ if (!function_exists('session')) {
287
+ /**
288
+ * Session管理
289
+ * @param string|array $name session名称,如果为数组表示进行session设置
290
+ * @param mixed $value session值
291
+ * @param string $prefix 前缀
292
+ * @return mixed
293
+ */
294
+ function session($name, $value = '', $prefix = null)
295
+ {
296
+ if (is_array($name)) {
297
+ // 初始化
298
+ Session::init($name);
299
+ } elseif (is_null($name)) {
300
+ // 清除
301
+ Session::clear('' === $value ? null : $value);
302
+ } elseif ('' === $value) {
303
+ // 判断或获取
304
+ return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix);
305
+ } elseif (is_null($value)) {
306
+ // 删除
307
+ return Session::delete($name, $prefix);
308
+ } else {
309
+ // 设置
310
+ return Session::set($name, $value, $prefix);
311
+ }
312
+ }
313
+ }
314
+
315
+ if (!function_exists('cookie')) {
316
+ /**
317
+ * Cookie管理
318
+ * @param string|array $name cookie名称,如果为数组表示进行cookie设置
319
+ * @param mixed $value cookie值
320
+ * @param mixed $option 参数
321
+ * @return mixed
322
+ */
323
+ function cookie($name, $value = '', $option = null)
324
+ {
325
+ if (is_array($name)) {
326
+ // 初始化
327
+ Cookie::init($name);
328
+ } elseif (is_null($name)) {
329
+ // 清除
330
+ Cookie::clear($value);
331
+ } elseif ('' === $value) {
332
+ // 获取
333
+ return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option);
334
+ } elseif (is_null($value)) {
335
+ // 删除
336
+ return Cookie::delete($name);
337
+ } else {
338
+ // 设置
339
+ return Cookie::set($name, $value, $option);
340
+ }
341
+ }
342
+ }
343
+
344
+ if (!function_exists('cache')) {
345
+ /**
346
+ * 缓存管理
347
+ * @param mixed $name 缓存名称,如果为数组表示进行缓存设置
348
+ * @param mixed $value 缓存值
349
+ * @param mixed $options 缓存参数
350
+ * @param string $tag 缓存标签
351
+ * @return mixed
352
+ */
353
+ function cache($name, $value = '', $options = null, $tag = null)
354
+ {
355
+ if (is_array($options)) {
356
+ // 缓存操作的同时初始化
357
+ $cache = Cache::connect($options);
358
+ } elseif (is_array($name)) {
359
+ // 缓存初始化
360
+ return Cache::connect($name);
361
+ } else {
362
+ $cache = Cache::init();
363
+ }
364
+
365
+ if (is_null($name)) {
366
+ return $cache->clear($value);
367
+ } elseif ('' === $value) {
368
+ // 获取缓存
369
+ return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name);
370
+ } elseif (is_null($value)) {
371
+ // 删除缓存
372
+ return $cache->rm($name);
373
+ } elseif (0 === strpos($name, '?') && '' !== $value) {
374
+ $expire = is_numeric($options) ? $options : null;
375
+ return $cache->remember(substr($name, 1), $value, $expire);
376
+ } else {
377
+ // 缓存数据
378
+ if (is_array($options)) {
379
+ $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间
380
+ } else {
381
+ $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间
382
+ }
383
+ if (is_null($tag)) {
384
+ return $cache->set($name, $value, $expire);
385
+ } else {
386
+ return $cache->tag($tag)->set($name, $value, $expire);
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ if (!function_exists('trace')) {
393
+ /**
394
+ * 记录日志信息
395
+ * @param mixed $log log信息 支持字符串和数组
396
+ * @param string $level 日志级别
397
+ * @return void|array
398
+ */
399
+ function trace($log = '[think]', $level = 'log')
400
+ {
401
+ if ('[think]' === $log) {
402
+ return Log::getLog();
403
+ } else {
404
+ Log::record($log, $level);
405
+ }
406
+ }
407
+ }
408
+
409
+ if (!function_exists('request')) {
410
+ /**
411
+ * 获取当前Request对象实例
412
+ * @return Request
413
+ */
414
+ function request()
415
+ {
416
+ return Request::instance();
417
+ }
418
+ }
419
+
420
+ if (!function_exists('response')) {
421
+ /**
422
+ * 创建普通 Response 对象实例
423
+ * @param mixed $data 输出数据
424
+ * @param int|string $code 状态码
425
+ * @param array $header 头信息
426
+ * @param string $type
427
+ * @return Response
428
+ */
429
+ function response($data = [], $code = 200, $header = [], $type = 'html')
430
+ {
431
+ return Response::create($data, $type, $code, $header);
432
+ }
433
+ }
434
+
435
+ if (!function_exists('view')) {
436
+ /**
437
+ * 渲染模板输出
438
+ * @param string $template 模板文件
439
+ * @param array $vars 模板变量
440
+ * @param array $replace 模板替换
441
+ * @param integer $code 状态码
442
+ * @return \think\response\View
443
+ */
444
+ function view($template = '', $vars = [], $replace = [], $code = 200)
445
+ {
446
+ return Response::create($template, 'view', $code)->replace($replace)->assign($vars);
447
+ }
448
+ }
449
+
450
+ if (!function_exists('json')) {
451
+ /**
452
+ * 获取\think\response\Json对象实例
453
+ * @param mixed $data 返回的数据
454
+ * @param integer $code 状态码
455
+ * @param array $header 头部
456
+ * @param array $options 参数
457
+ * @return \think\response\Json
458
+ */
459
+ function json($data = [], $code = 200, $header = [], $options = [])
460
+ {
461
+ return Response::create($data, 'json', $code, $header, $options);
462
+ }
463
+ }
464
+
465
+ if (!function_exists('jsonp')) {
466
+ /**
467
+ * 获取\think\response\Jsonp对象实例
468
+ * @param mixed $data 返回的数据
469
+ * @param integer $code 状态码
470
+ * @param array $header 头部
471
+ * @param array $options 参数
472
+ * @return \think\response\Jsonp
473
+ */
474
+ function jsonp($data = [], $code = 200, $header = [], $options = [])
475
+ {
476
+ return Response::create($data, 'jsonp', $code, $header, $options);
477
+ }
478
+ }
479
+
480
+ if (!function_exists('xml')) {
481
+ /**
482
+ * 获取\think\response\Xml对象实例
483
+ * @param mixed $data 返回的数据
484
+ * @param integer $code 状态码
485
+ * @param array $header 头部
486
+ * @param array $options 参数
487
+ * @return \think\response\Xml
488
+ */
489
+ function xml($data = [], $code = 200, $header = [], $options = [])
490
+ {
491
+ return Response::create($data, 'xml', $code, $header, $options);
492
+ }
493
+ }
494
+
495
+ if (!function_exists('redirect')) {
496
+ /**
497
+ * 获取\think\response\Redirect对象实例
498
+ * @param mixed $url 重定向地址 支持Url::build方法的地址
499
+ * @param array|integer $params 额外参数
500
+ * @param integer $code 状态码
501
+ * @param array $with 隐式传参
502
+ * @return \think\response\Redirect
503
+ */
504
+ function redirect($url = [], $params = [], $code = 302, $with = [])
505
+ {
506
+ if (is_integer($params)) {
507
+ $code = $params;
508
+ $params = [];
509
+ }
510
+ return Response::create($url, 'redirect', $code)->params($params)->with($with);
511
+ }
512
+ }
513
+
514
+ if (!function_exists('abort')) {
515
+ /**
516
+ * 抛出HTTP异常
517
+ * @param integer|Response $code 状态码 或者 Response对象实例
518
+ * @param string $message 错误信息
519
+ * @param array $header 参数
520
+ */
521
+ function abort($code, $message = null, $header = [])
522
+ {
523
+ if ($code instanceof Response) {
524
+ throw new HttpResponseException($code);
525
+ } else {
526
+ throw new HttpException($code, $message, null, $header);
527
+ }
528
+ }
529
+ }
530
+
531
+ if (!function_exists('halt')) {
532
+ /**
533
+ * 调试变量并且中断输出
534
+ * @param mixed $var 调试变量或者信息
535
+ */
536
+ function halt($var)
537
+ {
538
+ dump($var);
539
+ throw new HttpResponseException(new Response);
540
+ }
541
+ }
542
+
543
+ if (!function_exists('token')) {
544
+ /**
545
+ * 生成表单令牌
546
+ * @param string $name 令牌名称
547
+ * @param mixed $type 令牌生成方法
548
+ * @return string
549
+ */
550
+ function token($name = '__token__', $type = 'md5')
551
+ {
552
+ $token = Request::instance()->token($name, $type);
553
+ return '<input type="hidden" name="' . $name . '" value="' . $token . '" />';
554
+ }
555
+ }
556
+
557
+ if (!function_exists('load_relation')) {
558
+ /**
559
+ * 延迟预载入关联查询
560
+ * @param mixed $resultSet 数据集
561
+ * @param mixed $relation 关联
562
+ * @return array
563
+ */
564
+ function load_relation($resultSet, $relation)
565
+ {
566
+ $item = current($resultSet);
567
+ if ($item instanceof Model) {
568
+ $item->eagerlyResultSet($resultSet, $relation);
569
+ }
570
+ return $resultSet;
571
+ }
572
+ }
573
+
574
+ if (!function_exists('collection')) {
575
+ /**
576
+ * 数组转换为数据集对象
577
+ * @param array $resultSet 数据集数组
578
+ * @return \think\model\Collection|\think\Collection
579
+ */
580
+ function collection($resultSet)
581
+ {
582
+ $item = current($resultSet);
583
+ if ($item instanceof Model) {
584
+ return \think\model\Collection::make($resultSet);
585
+ } else {
586
+ return \think\Collection::make($resultSet);
587
+ }
588
+ }
589
+ }
thinkphp/lang/zh-cn.php ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ // 核心中文语言包
13
+ return [
14
+ // 系统错误提示
15
+ 'Undefined variable' => '未定义变量',
16
+ 'Undefined index' => '未定义数组索引',
17
+ 'Undefined offset' => '未定义数组下标',
18
+ 'Parse error' => '语法解析错误',
19
+ 'Type error' => '类型错误',
20
+ 'Fatal error' => '致命错误',
21
+ 'syntax error' => '语法错误',
22
+
23
+ // 框架核心错误提示
24
+ 'dispatch type not support' => '不支持的调度类型',
25
+ 'method param miss' => '方法参数错误',
26
+ 'method not exists' => '方法不存在',
27
+ 'module not exists' => '模块不存在',
28
+ 'controller not exists' => '控制器不存在',
29
+ 'class not exists' => '类不存在',
30
+ 'property not exists' => '类的属性不存在',
31
+ 'template not exists' => '模板文件不存在',
32
+ 'illegal controller name' => '非法的控制器名称',
33
+ 'illegal action name' => '非法的操作名称',
34
+ 'url suffix deny' => '禁止的URL后缀访问',
35
+ 'Route Not Found' => '当前访问路由未定义',
36
+ 'Undefined db type' => '未定义数据库类型',
37
+ 'variable type error' => '变量类型错误',
38
+ 'PSR-4 error' => 'PSR-4 规范错误',
39
+ 'not support total' => '简洁模式下不能获取数据总数',
40
+ 'not support last' => '简洁模式下不能获取最后一页',
41
+ 'error session handler' => '错误的SESSION处理器类',
42
+ 'not allow php tag' => '模板不允许使用PHP语法',
43
+ 'not support' => '不支持',
44
+ 'redisd master' => 'Redisd 主服务器错误',
45
+ 'redisd slave' => 'Redisd 从服务器错误',
46
+ 'must run at sae' => '必须在SAE运行',
47
+ 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务',
48
+ 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
49
+ 'fields not exists' => '数据表字段不存在',
50
+ 'where express error' => '查询表达式错误',
51
+ 'not support data' => '不支持的数据表达式',
52
+ 'no data to update' => '没有任何数据需要更新',
53
+ 'miss data to insert' => '缺少需要写入的数据',
54
+ 'miss complex primary data' => '缺少复合主键数据',
55
+ 'miss update condition' => '缺少更新条件',
56
+ 'model data Not Found' => '模型数据不存在',
57
+ 'table data not Found' => '表数据不存在',
58
+ 'delete without condition' => '没有条件不会执行删除操作',
59
+ 'miss relation data' => '缺少关联表数据',
60
+ 'tag attr must' => '模板标签属性必须',
61
+ 'tag error' => '模板标签错误',
62
+ 'cache write error' => '缓存写入失败',
63
+ 'sae mc write error' => 'SAE mc 写入错误',
64
+ 'route name not exists' => '路由标识不存在(或参数不够)',
65
+ 'invalid request' => '非法请求',
66
+ 'bind attr has exists' => '模型的属性已经存在',
67
+ 'relation data not exists' => '关联数据不存在',
68
+ 'relation not support' => '关联不支持',
69
+ 'chunk not support order' => 'Chunk不支持调用order方法',
70
+ 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key',
71
+
72
+ // 上传错误信息
73
+ 'unknown upload error' => '未知上传错误!',
74
+ 'file write error' => '文件写入失败!',
75
+ 'upload temp dir not found' => '找不到临时文件夹!',
76
+ 'no file to uploaded' => '没有文件被上传!',
77
+ 'only the portion of file is uploaded' => '文件只有部分被上传!',
78
+ 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!',
79
+ 'upload write error' => '文件上传保存错误!',
80
+ 'has the same filename: {:filename}' => '存在同名文件:{:filename}',
81
+ 'upload illegal files' => '非法上传文件',
82
+ 'illegal image files' => '非法图片文件',
83
+ 'extensions to upload is not allowed' => '上传文件后缀不允许',
84
+ 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!',
85
+ 'filesize not match' => '上传文件大小不符!',
86
+ 'directory {:path} creation failed' => '目录 {:path} 创建失败!',
87
+
88
+ // Validate Error Message
89
+ ':attribute require' => ':attribute不能为空',
90
+ ':attribute must be numeric' => ':attribute必须是数字',
91
+ ':attribute must be integer' => ':attribute必须是整数',
92
+ ':attribute must be float' => ':attribute必须是浮点数',
93
+ ':attribute must be bool' => ':attribute必须是布尔值',
94
+ ':attribute not a valid email address' => ':attribute格式不符',
95
+ ':attribute not a valid mobile' => ':attribute格式不符',
96
+ ':attribute must be a array' => ':attribute必须是数组',
97
+ ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1',
98
+ ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式',
99
+ ':attribute not a valid file' => ':attribute不是有效的上传文件',
100
+ ':attribute not a valid image' => ':attribute不是有效的图像文件',
101
+ ':attribute must be alpha' => ':attribute只能是字母',
102
+ ':attribute must be alpha-numeric' => ':attribute只能是字母和数字',
103
+ ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-',
104
+ ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP',
105
+ ':attribute must be chinese' => ':attribute只能是汉字',
106
+ ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母',
107
+ ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字',
108
+ ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
109
+ ':attribute not a valid url' => ':attribute不是有效的URL地址',
110
+ ':attribute not a valid ip' => ':attribute不是有效的IP地址',
111
+ ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule',
112
+ ':attribute must be in :rule' => ':attribute必须在 :rule 范围内',
113
+ ':attribute be notin :rule' => ':attribute不能在 :rule 范围内',
114
+ ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间',
115
+ ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间',
116
+ 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule',
117
+ 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule',
118
+ 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule',
119
+ ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule',
120
+ ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule',
121
+ ':attribute not within :rule' => '不在有效期内 :rule',
122
+ 'access IP is not allowed' => '不允许的IP访问',
123
+ 'access IP denied' => '禁止的IP访问',
124
+ ':attribute out of accord with :2' => ':attribute和确认字段:2不一致',
125
+ ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同',
126
+ ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule',
127
+ ':attribute must greater than :rule' => ':attribute必须大于 :rule',
128
+ ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule',
129
+ ':attribute must less than :rule' => ':attribute必须小于 :rule',
130
+ ':attribute must equal :rule' => ':attribute必须等于 :rule',
131
+ ':attribute has exists' => ':attribute已存在',
132
+ ':attribute not conform to the rules' => ':attribute不符合指定规则',
133
+ 'invalid Request method' => '无效的请求类型',
134
+ 'invalid token' => '令牌数据无效',
135
+ 'not conform to the rules' => '规则错误',
136
+ 'Please copy file' => '请复制',
137
+ ];
thinkphp/library/think/App.php ADDED
@@ -0,0 +1,677 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\ClassNotFoundException;
15
+ use think\exception\HttpException;
16
+ use think\exception\HttpResponseException;
17
+ use think\exception\RouteNotFoundException;
18
+
19
+ /**
20
+ * App 应用管理
21
+ * @author liu21st <liu21st@gmail.com>
22
+ */
23
+ class App
24
+ {
25
+ /**
26
+ * @var bool 是否初始化过
27
+ */
28
+ protected static $init = false;
29
+
30
+ /**
31
+ * @var string 当前模块路径
32
+ */
33
+ public static $modulePath;
34
+
35
+ /**
36
+ * @var bool 应用调试模式
37
+ */
38
+ public static $debug = true;
39
+
40
+ /**
41
+ * @var string 应用类库命名空间
42
+ */
43
+ public static $namespace = 'app';
44
+
45
+ /**
46
+ * @var bool 应用类库后缀
47
+ */
48
+ public static $suffix = false;
49
+
50
+ /**
51
+ * @var bool 应用路由检测
52
+ */
53
+ protected static $routeCheck;
54
+
55
+ /**
56
+ * @var bool 严格路由检测
57
+ */
58
+ protected static $routeMust;
59
+
60
+ /**
61
+ * @var array 请求调度分发
62
+ */
63
+ protected static $dispatch;
64
+
65
+ /**
66
+ * @var array 额外加载文件
67
+ */
68
+ protected static $file = [];
69
+
70
+ /**
71
+ * 执行应用程序
72
+ * @access public
73
+ * @param Request $request 请求对象
74
+ * @return Response
75
+ * @throws Exception
76
+ */
77
+ public static function run(Request $request = null)
78
+ {
79
+ $request = is_null($request) ? Request::instance() : $request;
80
+
81
+ try {
82
+ $config = self::initCommon();
83
+
84
+ // 模块/控制器绑定
85
+ if (defined('BIND_MODULE')) {
86
+ BIND_MODULE && Route::bind(BIND_MODULE);
87
+ } elseif ($config['auto_bind_module']) {
88
+ // 入口自动绑定
89
+ $name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
90
+ if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
91
+ Route::bind($name);
92
+ }
93
+ }
94
+
95
+ $request->filter($config['default_filter']);
96
+
97
+ // 默认语言
98
+ Lang::range($config['default_lang']);
99
+ // 开启多语言机制 检测当前语言
100
+ $config['lang_switch_on'] && Lang::detect();
101
+ $request->langset(Lang::range());
102
+
103
+ // 加载系统语言包
104
+ Lang::load([
105
+ THINK_PATH . 'lang' . DS . $request->langset() . EXT,
106
+ APP_PATH . 'lang' . DS . $request->langset() . EXT,
107
+ ]);
108
+
109
+ // 监听 app_dispatch
110
+ Hook::listen('app_dispatch', self::$dispatch);
111
+ // 获取应用调度信息
112
+ $dispatch = self::$dispatch;
113
+
114
+ // 未设置调度信息则进行 URL 路由检测
115
+ if (empty($dispatch)) {
116
+ $dispatch = self::routeCheck($request, $config);
117
+ }
118
+
119
+ // 记录当前调度信息
120
+ $request->dispatch($dispatch);
121
+
122
+ // 记录路由和请求信息
123
+ if (self::$debug) {
124
+ Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
125
+ Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
126
+ Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
127
+ }
128
+
129
+ // 监听 app_begin
130
+ Hook::listen('app_begin', $dispatch);
131
+
132
+ // 请求缓存检查
133
+ $request->cache(
134
+ $config['request_cache'],
135
+ $config['request_cache_expire'],
136
+ $config['request_cache_except']
137
+ );
138
+
139
+ $data = self::exec($dispatch, $config);
140
+ } catch (HttpResponseException $exception) {
141
+ $data = $exception->getResponse();
142
+ }
143
+
144
+ // 清空类的实例化
145
+ Loader::clearInstance();
146
+
147
+ // 输出数据到客户端
148
+ if ($data instanceof Response) {
149
+ $response = $data;
150
+ } elseif (!is_null($data)) {
151
+ // 默认自动识别响应输出类型
152
+ $type = $request->isAjax() ?
153
+ Config::get('default_ajax_return') :
154
+ Config::get('default_return_type');
155
+
156
+ $response = Response::create($data, $type);
157
+ } else {
158
+ $response = Response::create();
159
+ }
160
+
161
+ // 监听 app_end
162
+ Hook::listen('app_end', $response);
163
+
164
+ return $response;
165
+ }
166
+
167
+ /**
168
+ * 初始化应用,并返回配置信息
169
+ * @access public
170
+ * @return array
171
+ */
172
+ public static function initCommon()
173
+ {
174
+ if (empty(self::$init)) {
175
+ if (defined('APP_NAMESPACE')) {
176
+ self::$namespace = APP_NAMESPACE;
177
+ }
178
+
179
+ Loader::addNamespace(self::$namespace, APP_PATH);
180
+
181
+ // 初始化应用
182
+ $config = self::init();
183
+ self::$suffix = $config['class_suffix'];
184
+
185
+ // 应用调试模式
186
+ self::$debug = Env::get('app_debug', Config::get('app_debug'));
187
+
188
+ if (!self::$debug) {
189
+ ini_set('display_errors', 'Off');
190
+ } elseif (!IS_CLI) {
191
+ // 重新申请一块比较大的 buffer
192
+ if (ob_get_level() > 0) {
193
+ $output = ob_get_clean();
194
+ }
195
+
196
+ ob_start();
197
+
198
+ if (!empty($output)) {
199
+ echo $output;
200
+ }
201
+
202
+ }
203
+
204
+ if (!empty($config['root_namespace'])) {
205
+ Loader::addNamespace($config['root_namespace']);
206
+ }
207
+
208
+ // 加载额外文件
209
+ if (!empty($config['extra_file_list'])) {
210
+ foreach ($config['extra_file_list'] as $file) {
211
+ $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;
212
+ if (is_file($file) && !isset(self::$file[$file])) {
213
+ include $file;
214
+ self::$file[$file] = true;
215
+ }
216
+ }
217
+ }
218
+
219
+ // 设置系统时区
220
+ date_default_timezone_set($config['default_timezone']);
221
+
222
+ // 监听 app_init
223
+ Hook::listen('app_init');
224
+
225
+ self::$init = true;
226
+ }
227
+
228
+ return Config::get();
229
+ }
230
+
231
+ /**
232
+ * 初始化应用或模块
233
+ * @access public
234
+ * @param string $module 模块名
235
+ * @return array
236
+ */
237
+ private static function init($module = '')
238
+ {
239
+ // 定位模块目录
240
+ $module = $module ? $module . DS : '';
241
+
242
+ // 加载初始化文件
243
+ if (is_file(APP_PATH . $module . 'init' . EXT)) {
244
+ include APP_PATH . $module . 'init' . EXT;
245
+ } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) {
246
+ include RUNTIME_PATH . $module . 'init' . EXT;
247
+ } else {
248
+ // 加载模块配置
249
+ $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT);
250
+
251
+ // 读取数据库配置文件
252
+ $filename = CONF_PATH . $module . 'database' . CONF_EXT;
253
+ Config::load($filename, 'database');
254
+
255
+ // 读取扩展配置文件
256
+ if (is_dir(CONF_PATH . $module . 'extra')) {
257
+ $dir = CONF_PATH . $module . 'extra';
258
+ $files = scandir($dir);
259
+ foreach ($files as $file) {
260
+ if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) {
261
+ $filename = $dir . DS . $file;
262
+ Config::load($filename, pathinfo($file, PATHINFO_FILENAME));
263
+ }
264
+ }
265
+ }
266
+
267
+ // 加载应用状态配置
268
+ if ($config['app_status']) {
269
+ Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
270
+ }
271
+
272
+ // 加载行为扩展文件
273
+ if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
274
+ Hook::import(include CONF_PATH . $module . 'tags' . EXT);
275
+ }
276
+
277
+ // 加载公共文件
278
+ $path = APP_PATH . $module;
279
+ if (is_file($path . 'common' . EXT)) {
280
+ include $path . 'common' . EXT;
281
+ }
282
+
283
+ // 加载当前模块语言包
284
+ if ($module) {
285
+ Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT);
286
+ }
287
+ }
288
+
289
+ return Config::get();
290
+ }
291
+
292
+ /**
293
+ * 设置当前请求的调度信息
294
+ * @access public
295
+ * @param array|string $dispatch 调度信息
296
+ * @param string $type 调度类型
297
+ * @return void
298
+ */
299
+ public static function dispatch($dispatch, $type = 'module')
300
+ {
301
+ self::$dispatch = ['type' => $type, $type => $dispatch];
302
+ }
303
+
304
+ /**
305
+ * 执行函数或者闭包方法 支持参数调用
306
+ * @access public
307
+ * @param string|array|\Closure $function 函数或者闭包
308
+ * @param array $vars 变量
309
+ * @return mixed
310
+ */
311
+ public static function invokeFunction($function, $vars = [])
312
+ {
313
+ $reflect = new \ReflectionFunction($function);
314
+ $args = self::bindParams($reflect, $vars);
315
+
316
+ // 记录执行信息
317
+ self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
318
+
319
+ return $reflect->invokeArgs($args);
320
+ }
321
+
322
+ /**
323
+ * 调用反射执行类的方法 支持参数绑定
324
+ * @access public
325
+ * @param string|array $method 方法
326
+ * @param array $vars 变量
327
+ * @return mixed
328
+ */
329
+ public static function invokeMethod($method, $vars = [])
330
+ {
331
+ if (is_array($method)) {
332
+ $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
333
+ $reflect = new \ReflectionMethod($class, $method[1]);
334
+ } else {
335
+ // 静态方法
336
+ $reflect = new \ReflectionMethod($method);
337
+ }
338
+
339
+ $args = self::bindParams($reflect, $vars);
340
+
341
+ self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
342
+
343
+ return $reflect->invokeArgs(isset($class) ? $class : null, $args);
344
+ }
345
+
346
+ /**
347
+ * 调用反射执行类的实例化 支持依赖注入
348
+ * @access public
349
+ * @param string $class 类名
350
+ * @param array $vars 变量
351
+ * @return mixed
352
+ */
353
+ public static function invokeClass($class, $vars = [])
354
+ {
355
+ $reflect = new \ReflectionClass($class);
356
+ $constructor = $reflect->getConstructor();
357
+ $args = $constructor ? self::bindParams($constructor, $vars) : [];
358
+
359
+ return $reflect->newInstanceArgs($args);
360
+ }
361
+
362
+ /**
363
+ * 绑定参数
364
+ * @access private
365
+ * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
366
+ * @param array $vars 变量
367
+ * @return array
368
+ */
369
+ private static function bindParams($reflect, $vars = [])
370
+ {
371
+ // 自动获取请求变量
372
+ if (empty($vars)) {
373
+ $vars = Config::get('url_param_type') ?
374
+ Request::instance()->route() :
375
+ Request::instance()->param();
376
+ }
377
+
378
+ $args = [];
379
+ if ($reflect->getNumberOfParameters() > 0) {
380
+ // 判断数组类型 数字数组时按顺序绑定参数
381
+ reset($vars);
382
+ $type = key($vars) === 0 ? 1 : 0;
383
+
384
+ foreach ($reflect->getParameters() as $param) {
385
+ $args[] = self::getParamValue($param, $vars, $type);
386
+ }
387
+ }
388
+
389
+ return $args;
390
+ }
391
+
392
+ /**
393
+ * 获取参数值
394
+ * @access private
395
+ * @param \ReflectionParameter $param 参数
396
+ * @param array $vars 变量
397
+ * @param string $type 类别
398
+ * @return array
399
+ */
400
+ private static function getParamValue($param, &$vars, $type)
401
+ {
402
+ $name = $param->getName();
403
+ $class = $param->getClass();
404
+
405
+ if ($class) {
406
+ $className = $class->getName();
407
+ $bind = Request::instance()->$name;
408
+
409
+ if ($bind instanceof $className) {
410
+ $result = $bind;
411
+ } else {
412
+ if (method_exists($className, 'invoke')) {
413
+ $method = new \ReflectionMethod($className, 'invoke');
414
+
415
+ if ($method->isPublic() && $method->isStatic()) {
416
+ return $className::invoke(Request::instance());
417
+ }
418
+ }
419
+
420
+ $result = method_exists($className, 'instance') ?
421
+ $className::instance() :
422
+ new $className;
423
+ }
424
+ } elseif (1 == $type && !empty($vars)) {
425
+ $result = array_shift($vars);
426
+ } elseif (0 == $type && isset($vars[$name])) {
427
+ $result = $vars[$name];
428
+ } elseif ($param->isDefaultValueAvailable()) {
429
+ $result = $param->getDefaultValue();
430
+ } else {
431
+ throw new \InvalidArgumentException('method param miss:' . $name);
432
+ }
433
+
434
+ return $result;
435
+ }
436
+
437
+ /**
438
+ * 执行调用分发
439
+ * @access protected
440
+ * @param array $dispatch 调用信息
441
+ * @param array $config 配置信息
442
+ * @return Response|mixed
443
+ * @throws \InvalidArgumentException
444
+ */
445
+ protected static function exec($dispatch, $config)
446
+ {
447
+ switch ($dispatch['type']) {
448
+ case 'redirect': // 重定向跳转
449
+ $data = Response::create($dispatch['url'], 'redirect')
450
+ ->code($dispatch['status']);
451
+ break;
452
+ case 'module': // 模块/控制器/操作
453
+ $data = self::module(
454
+ $dispatch['module'],
455
+ $config,
456
+ isset($dispatch['convert']) ? $dispatch['convert'] : null
457
+ );
458
+ break;
459
+ case 'controller': // 执行控制器操作
460
+ $vars = array_merge(Request::instance()->param(), $dispatch['var']);
461
+ $data = Loader::action(
462
+ $dispatch['controller'],
463
+ $vars,
464
+ $config['url_controller_layer'],
465
+ $config['controller_suffix']
466
+ );
467
+ break;
468
+ case 'method': // 回调方法
469
+ $vars = array_merge(Request::instance()->param(), $dispatch['var']);
470
+ $data = self::invokeMethod($dispatch['method'], $vars);
471
+ break;
472
+ case 'function': // 闭包
473
+ $data = self::invokeFunction($dispatch['function']);
474
+ break;
475
+ case 'response': // Response 实例
476
+ $data = $dispatch['response'];
477
+ break;
478
+ default:
479
+ throw new \InvalidArgumentException('dispatch type not support');
480
+ }
481
+
482
+ return $data;
483
+ }
484
+
485
+ /**
486
+ * 执行模块
487
+ * @access public
488
+ * @param array $result 模块/控制器/操作
489
+ * @param array $config 配置参数
490
+ * @param bool $convert 是否自动转换控制器和操作名
491
+ * @return mixed
492
+ * @throws HttpException
493
+ */
494
+ public static function module($result, $config, $convert = null)
495
+ {
496
+ if (is_string($result)) {
497
+ $result = explode('/', $result);
498
+ }
499
+
500
+ $request = Request::instance();
501
+
502
+ if ($config['app_multi_module']) {
503
+ // 多模块部署
504
+ $module = strip_tags(strtolower($result[0] ?: $config['default_module']));
505
+ $bind = Route::getBind('module');
506
+ $available = false;
507
+
508
+ if ($bind) {
509
+ // 绑定模块
510
+ list($bindModule) = explode('/', $bind);
511
+
512
+ if (empty($result[0])) {
513
+ $module = $bindModule;
514
+ $available = true;
515
+ } elseif ($module == $bindModule) {
516
+ $available = true;
517
+ }
518
+ } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
519
+ $available = true;
520
+ }
521
+
522
+ // 模块初始化
523
+ if ($module && $available) {
524
+ // 初始化模块
525
+ $request->module($module);
526
+ $config = self::init($module);
527
+
528
+ // 模块请求缓存检查
529
+ $request->cache(
530
+ $config['request_cache'],
531
+ $config['request_cache_expire'],
532
+ $config['request_cache_except']
533
+ );
534
+ } else {
535
+ throw new HttpException(404, 'module not exists:' . $module);
536
+ }
537
+ } else {
538
+ // 单一模块部署
539
+ $module = '';
540
+ $request->module($module);
541
+ }
542
+
543
+ // 设置默认过滤机制
544
+ $request->filter($config['default_filter']);
545
+
546
+ // 当前模块路径
547
+ App::$modulePath = APP_PATH . ($module ? $module . DS : '');
548
+
549
+ // 是否自动转换控制器和操作名
550
+ $convert = is_bool($convert) ? $convert : $config['url_convert'];
551
+
552
+ // 获取控制器名
553
+ $controller = strip_tags($result[1] ?: $config['default_controller']);
554
+
555
+ if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
556
+ throw new HttpException(404, 'controller not exists:' . $controller);
557
+ }
558
+
559
+ $controller = $convert ? strtolower($controller) : $controller;
560
+
561
+ // 获取操作名
562
+ $actionName = strip_tags($result[2] ?: $config['default_action']);
563
+ if (!empty($config['action_convert'])) {
564
+ $actionName = Loader::parseName($actionName, 1);
565
+ } else {
566
+ $actionName = $convert ? strtolower($actionName) : $actionName;
567
+ }
568
+
569
+ // 设置当前请求的控制器、操作
570
+ $request->controller(Loader::parseName($controller, 1))->action($actionName);
571
+
572
+ // 监听module_init
573
+ Hook::listen('module_init', $request);
574
+
575
+ try {
576
+ $instance = Loader::controller(
577
+ $controller,
578
+ $config['url_controller_layer'],
579
+ $config['controller_suffix'],
580
+ $config['empty_controller']
581
+ );
582
+ } catch (ClassNotFoundException $e) {
583
+ throw new HttpException(404, 'controller not exists:' . $e->getClass());
584
+ }
585
+
586
+ // 获取当前操作名
587
+ $action = $actionName . $config['action_suffix'];
588
+
589
+ $vars = [];
590
+ if (is_callable([$instance, $action])) {
591
+ // 执行操作方法
592
+ $call = [$instance, $action];
593
+ // 严格获取当前操作方法名
594
+ $reflect = new \ReflectionMethod($instance, $action);
595
+ $methodName = $reflect->getName();
596
+ $suffix = $config['action_suffix'];
597
+ $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
598
+ $request->action($actionName);
599
+
600
+ } elseif (is_callable([$instance, '_empty'])) {
601
+ // 空操作
602
+ $call = [$instance, '_empty'];
603
+ $vars = [$actionName];
604
+ } else {
605
+ // 操作不存在
606
+ throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
607
+ }
608
+
609
+ Hook::listen('action_begin', $call);
610
+
611
+ return self::invokeMethod($call, $vars);
612
+ }
613
+
614
+ /**
615
+ * URL路由检测(根据PATH_INFO)
616
+ * @access public
617
+ * @param \think\Request $request 请求实例
618
+ * @param array $config 配置信息
619
+ * @return array
620
+ * @throws \think\Exception
621
+ */
622
+ public static function routeCheck($request, array $config)
623
+ {
624
+ $path = $request->path();
625
+ $depr = $config['pathinfo_depr'];
626
+ $result = false;
627
+
628
+ // 路由检测
629
+ $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
630
+ if ($check) {
631
+ // 开启路由
632
+ if (is_file(RUNTIME_PATH . 'route.php')) {
633
+ // 读取路由缓存
634
+ $rules = include RUNTIME_PATH . 'route.php';
635
+ is_array($rules) && Route::rules($rules);
636
+ } else {
637
+ $files = $config['route_config_file'];
638
+ foreach ($files as $file) {
639
+ if (is_file(CONF_PATH . $file . CONF_EXT)) {
640
+ // 导入路由配置
641
+ $rules = include CONF_PATH . $file . CONF_EXT;
642
+ is_array($rules) && Route::import($rules);
643
+ }
644
+ }
645
+ }
646
+
647
+ // 路由检测(根据路由定义返回不同的URL调度)
648
+ $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
649
+ $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
650
+
651
+ if ($must && false === $result) {
652
+ // 路由无效
653
+ throw new RouteNotFoundException();
654
+ }
655
+ }
656
+
657
+ // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
658
+ if (false === $result) {
659
+ $result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
660
+ }
661
+
662
+ return $result;
663
+ }
664
+
665
+ /**
666
+ * 设置应用的路由检测机制
667
+ * @access public
668
+ * @param bool $route 是否需要检测路由
669
+ * @param bool $must 是否强制检测路由
670
+ * @return void
671
+ */
672
+ public static function route($route, $must = false)
673
+ {
674
+ self::$routeCheck = $route;
675
+ self::$routeMust = $must;
676
+ }
677
+ }
thinkphp/library/think/Build.php ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Build
15
+ {
16
+ /**
17
+ * 根据传入的 build 资料创建目录和文件
18
+ * @access public
19
+ * @param array $build build 列表
20
+ * @param string $namespace 应用类库命名空间
21
+ * @param bool $suffix 类库后缀
22
+ * @return void
23
+ * @throws Exception
24
+ */
25
+ public static function run(array $build = [], $namespace = 'app', $suffix = false)
26
+ {
27
+ // 锁定
28
+ $lock = APP_PATH . 'build.lock';
29
+
30
+ // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了
31
+ if (!is_writable($lock)) {
32
+ if (!touch($lock)) {
33
+ throw new Exception(
34
+ '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~',
35
+ 10006
36
+ );
37
+ }
38
+
39
+ foreach ($build as $module => $list) {
40
+ if ('__dir__' == $module) {
41
+ // 创建目录列表
42
+ self::buildDir($list);
43
+ } elseif ('__file__' == $module) {
44
+ // 创建文件列表
45
+ self::buildFile($list);
46
+ } else {
47
+ // 创建模块
48
+ self::module($module, $list, $namespace, $suffix);
49
+ }
50
+ }
51
+
52
+ // 解除锁定
53
+ unlink($lock);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * 创建目录
59
+ * @access protected
60
+ * @param array $list 目录列表
61
+ * @return void
62
+ */
63
+ protected static function buildDir($list)
64
+ {
65
+ foreach ($list as $dir) {
66
+ // 目录不存在则创建目录
67
+ !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 创建文件
73
+ * @access protected
74
+ * @param array $list 文件列表
75
+ * @return void
76
+ */
77
+ protected static function buildFile($list)
78
+ {
79
+ foreach ($list as $file) {
80
+ // 先创建目录
81
+ if (!is_dir(APP_PATH . dirname($file))) {
82
+ mkdir(APP_PATH . dirname($file), 0755, true);
83
+ }
84
+
85
+ // 再创建文件
86
+ if (!is_file(APP_PATH . $file)) {
87
+ file_put_contents(
88
+ APP_PATH . $file,
89
+ 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "<?php\n" : ''
90
+ );
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 创建模块
97
+ * @access public
98
+ * @param string $module 模块名
99
+ * @param array $list build 列表
100
+ * @param string $namespace 应用类库命名空间
101
+ * @param bool $suffix 类库后缀
102
+ * @return void
103
+ */
104
+ public static function module($module = '', $list = [], $namespace = 'app', $suffix = false)
105
+ {
106
+ $module = $module ?: '';
107
+
108
+ // 创建模块目录
109
+ !is_dir(APP_PATH . $module) && mkdir(APP_PATH . $module);
110
+
111
+ // 如果不是 runtime 目录则需要创建配置文件和公共文件、创建模块的默认页面
112
+ if (basename(RUNTIME_PATH) != $module) {
113
+ self::buildCommon($module);
114
+ self::buildHello($module, $namespace, $suffix);
115
+ }
116
+
117
+ // 未指定文件和目录,则创建默认的模块目录和文件
118
+ if (empty($list)) {
119
+ $list = [
120
+ '__file__' => ['config.php', 'common.php'],
121
+ '__dir__' => ['controller', 'model', 'view'],
122
+ ];
123
+ }
124
+
125
+ // 创建子目录和文件
126
+ foreach ($list as $path => $file) {
127
+ $modulePath = APP_PATH . $module . DS;
128
+
129
+ if ('__dir__' == $path) {
130
+ // 生成子目录
131
+ foreach ($file as $dir) {
132
+ self::checkDirBuild($modulePath . $dir);
133
+ }
134
+ } elseif ('__file__' == $path) {
135
+ // 生成(空白)文件
136
+ foreach ($file as $name) {
137
+ if (!is_file($modulePath . $name)) {
138
+ file_put_contents(
139
+ $modulePath . $name,
140
+ 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "<?php\n" : ''
141
+ );
142
+ }
143
+ }
144
+ } else {
145
+ // 生成相关 MVC 文件
146
+ foreach ($file as $val) {
147
+ $val = trim($val);
148
+ $filename = $modulePath . $path . DS . $val . ($suffix ? ucfirst($path) : '') . EXT;
149
+ $space = $namespace . '\\' . ($module ? $module . '\\' : '') . $path;
150
+ $class = $val . ($suffix ? ucfirst($path) : '');
151
+
152
+ switch ($path) {
153
+ case 'controller': // 控制器
154
+ $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
155
+ break;
156
+ case 'model': // 模型
157
+ $content = "<?php\nnamespace {$space};\n\nuse think\Model;\n\nclass {$class} extends Model\n{\n\n}";
158
+ break;
159
+ case 'view': // 视图
160
+ $filename = $modulePath . $path . DS . $val . '.html';
161
+ self::checkDirBuild(dirname($filename));
162
+ $content = '';
163
+ break;
164
+ default:
165
+ // 其他文件
166
+ $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
167
+ }
168
+
169
+ if (!is_file($filename)) {
170
+ file_put_contents($filename, $content);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * 创建模块的欢迎页面
179
+ * @access protected
180
+ * @param string $module 模块名
181
+ * @param string $namespace 应用类库命名空间
182
+ * @param bool $suffix 类库后缀
183
+ * @return void
184
+ */
185
+ protected static function buildHello($module, $namespace, $suffix = false)
186
+ {
187
+ $filename = APP_PATH . ($module ? $module . DS : '') .
188
+ 'controller' . DS . 'Index' .
189
+ ($suffix ? 'Controller' : '') . EXT;
190
+
191
+ if (!is_file($filename)) {
192
+ $module = $module ? $module . '\\' : '';
193
+ $suffix = $suffix ? 'Controller' : '';
194
+ $content = str_replace(
195
+ ['{$app}', '{$module}', '{layer}', '{$suffix}'],
196
+ [$namespace, $module, 'controller', $suffix],
197
+ file_get_contents(THINK_PATH . 'tpl' . DS . 'default_index.tpl')
198
+ );
199
+
200
+ self::checkDirBuild(dirname($filename));
201
+ file_put_contents($filename, $content);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * 创建模块的公共文件
207
+ * @access protected
208
+ * @param string $module 模块名
209
+ * @return void
210
+ */
211
+ protected static function buildCommon($module)
212
+ {
213
+ $config = CONF_PATH . ($module ? $module . DS : '') . 'config.php';
214
+
215
+ self::checkDirBuild(dirname($config));
216
+
217
+ if (!is_file($config)) {
218
+ file_put_contents($config, "<?php\n//配置文件\nreturn [\n\n];");
219
+ }
220
+
221
+ $common = APP_PATH . ($module ? $module . DS : '') . 'common.php';
222
+ if (!is_file($common)) file_put_contents($common, "<?php\n");
223
+ }
224
+
225
+ /**
226
+ * 创建目录
227
+ * @access protected
228
+ * @param string $dirname 目录名称
229
+ * @return void
230
+ */
231
+ protected static function checkDirBuild($dirname)
232
+ {
233
+ !is_dir($dirname) && mkdir($dirname, 0755, true);
234
+ }
235
+ }
thinkphp/library/think/Cache.php ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\cache\Driver;
15
+
16
+ class Cache
17
+ {
18
+ /**
19
+ * @var array 缓存的实例
20
+ */
21
+ public static $instance = [];
22
+
23
+ /**
24
+ * @var int 缓存读取次数
25
+ */
26
+ public static $readTimes = 0;
27
+
28
+ /**
29
+ * @var int 缓存写入次数
30
+ */
31
+ public static $writeTimes = 0;
32
+
33
+ /**
34
+ * @var object 操作句柄
35
+ */
36
+ public static $handler;
37
+
38
+ /**
39
+ * 连接缓存驱动
40
+ * @access public
41
+ * @param array $options 配置数组
42
+ * @param bool|string $name 缓存连接标识 true 强制重新连接
43
+ * @return Driver
44
+ */
45
+ public static function connect(array $options = [], $name = false)
46
+ {
47
+ $type = !empty($options['type']) ? $options['type'] : 'File';
48
+
49
+ if (false === $name) {
50
+ $name = md5(serialize($options));
51
+ }
52
+
53
+ if (true === $name || !isset(self::$instance[$name])) {
54
+ $class = false === strpos($type, '\\') ?
55
+ '\\think\\cache\\driver\\' . ucwords($type) :
56
+ $type;
57
+
58
+ // 记录初始化信息
59
+ App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info');
60
+
61
+ if (true === $name) {
62
+ return new $class($options);
63
+ }
64
+
65
+ self::$instance[$name] = new $class($options);
66
+ }
67
+
68
+ return self::$instance[$name];
69
+ }
70
+
71
+ /**
72
+ * 自动初始化缓存
73
+ * @access public
74
+ * @param array $options 配置数组
75
+ * @return Driver
76
+ */
77
+ public static function init(array $options = [])
78
+ {
79
+ if (is_null(self::$handler)) {
80
+ if (empty($options) && 'complex' == Config::get('cache.type')) {
81
+ $default = Config::get('cache.default');
82
+ // 获取默认缓存配置,并连接
83
+ $options = Config::get('cache.' . $default['type']) ?: $default;
84
+ } elseif (empty($options)) {
85
+ $options = Config::get('cache');
86
+ }
87
+ self::$handler = self::connect($options);
88
+ }
89
+
90
+ return self::$handler;
91
+ }
92
+
93
+ public static function reset_init($options)
94
+ {
95
+ self::$handler = self::connect($options);
96
+ return self::$handler;
97
+ }
98
+ /**
99
+ * 切换缓存类型 需要配置 cache.type 为 complex
100
+ * @access public
101
+ * @param string $name 缓存标识
102
+ * @return Driver
103
+ */
104
+ public static function store($name = '')
105
+ {
106
+ if ('' !== $name && 'complex' == Config::get('cache.type')) {
107
+ return self::connect(Config::get('cache.' . $name), strtolower($name));
108
+ }
109
+
110
+ return self::init();
111
+ }
112
+
113
+ /**
114
+ * 判断缓存是否存在
115
+ * @access public
116
+ * @param string $name 缓存变量名
117
+ * @return bool
118
+ */
119
+ public static function has($name)
120
+ {
121
+ self::$readTimes++;
122
+
123
+ return self::init()->has($name);
124
+ }
125
+
126
+ /**
127
+ * 读取缓存
128
+ * @access public
129
+ * @param string $name 缓存标识
130
+ * @param mixed $default 默认值
131
+ * @return mixed
132
+ */
133
+ public static function get($name, $default = false)
134
+ {
135
+ self::$readTimes++;
136
+
137
+ return self::init()->get($name, $default);
138
+ }
139
+
140
+ /**
141
+ * 写入缓存
142
+ * @access public
143
+ * @param string $name 缓存标识
144
+ * @param mixed $value 存储数据
145
+ * @param int|null $expire 有效时间 0为永久
146
+ * @return boolean
147
+ */
148
+ public static function set($name, $value, $expire = null)
149
+ {
150
+ self::$writeTimes++;
151
+
152
+ return self::init()->set($name, $value, $expire);
153
+ }
154
+
155
+ /**
156
+ * 自增缓存(针对数值缓存)
157
+ * @access public
158
+ * @param string $name 缓存变量名
159
+ * @param int $step 步长
160
+ * @return false|int
161
+ */
162
+ public static function inc($name, $step = 1)
163
+ {
164
+ self::$writeTimes++;
165
+
166
+ return self::init()->inc($name, $step);
167
+ }
168
+
169
+ /**
170
+ * 自减缓存(针对数值缓存)
171
+ * @access public
172
+ * @param string $name 缓存变量名
173
+ * @param int $step 步长
174
+ * @return false|int
175
+ */
176
+ public static function dec($name, $step = 1)
177
+ {
178
+ self::$writeTimes++;
179
+
180
+ return self::init()->dec($name, $step);
181
+ }
182
+
183
+ /**
184
+ * 删除缓存
185
+ * @access public
186
+ * @param string $name 缓存标识
187
+ * @return boolean
188
+ */
189
+ public static function rm($name)
190
+ {
191
+ self::$writeTimes++;
192
+
193
+ return self::init()->rm($name);
194
+ }
195
+
196
+ /**
197
+ * 清除缓存
198
+ * @access public
199
+ * @param string $tag 标签名
200
+ * @return boolean
201
+ */
202
+ public static function clear($tag = null)
203
+ {
204
+ self::$writeTimes++;
205
+
206
+ return self::init()->clear($tag);
207
+ }
208
+
209
+ /**
210
+ * 读取缓存并删除
211
+ * @access public
212
+ * @param string $name 缓存变量名
213
+ * @return mixed
214
+ */
215
+ public static function pull($name)
216
+ {
217
+ self::$readTimes++;
218
+ self::$writeTimes++;
219
+
220
+ return self::init()->pull($name);
221
+ }
222
+
223
+ /**
224
+ * 如果不存在则写入缓存
225
+ * @access public
226
+ * @param string $name 缓存变量名
227
+ * @param mixed $value 存储数据
228
+ * @param int $expire 有效时间 0为永久
229
+ * @return mixed
230
+ */
231
+ public static function remember($name, $value, $expire = null)
232
+ {
233
+ self::$readTimes++;
234
+
235
+ return self::init()->remember($name, $value, $expire);
236
+ }
237
+
238
+ /**
239
+ * 缓存标签
240
+ * @access public
241
+ * @param string $name 标签名
242
+ * @param string|array $keys 缓存标识
243
+ * @param bool $overlay 是否覆盖
244
+ * @return Driver
245
+ */
246
+ public static function tag($name, $keys = null, $overlay = false)
247
+ {
248
+ return self::init()->tag($name, $keys, $overlay);
249
+ }
250
+
251
+ }
thinkphp/library/think/Collection.php ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: zhangyajun <448901948@qq.com>
10
+ // +----------------------------------------------------------------------
11
+
12
+ namespace think;
13
+
14
+ use ArrayAccess;
15
+ use ArrayIterator;
16
+ use Countable;
17
+ use IteratorAggregate;
18
+ use JsonSerializable;
19
+
20
+ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
21
+ {
22
+ /**
23
+ * @var array 数据
24
+ */
25
+ protected $items = [];
26
+
27
+ /**
28
+ * Collection constructor.
29
+ * @access public
30
+ * @param array $items 数据
31
+ */
32
+ public function __construct($items = [])
33
+ {
34
+ $this->items = $this->convertToArray($items);
35
+ }
36
+
37
+ /**
38
+ * 创建 Collection 实例
39
+ * @access public
40
+ * @param array $items 数据
41
+ * @return static
42
+ */
43
+ public static function make($items = [])
44
+ {
45
+ return new static($items);
46
+ }
47
+
48
+ /**
49
+ * 判断数据是否为空
50
+ * @access public
51
+ * @return bool
52
+ */
53
+ public function isEmpty()
54
+ {
55
+ return empty($this->items);
56
+ }
57
+
58
+ /**
59
+ * 将数据转成数组
60
+ * @access public
61
+ * @return array
62
+ */
63
+ public function toArray()
64
+ {
65
+ return array_map(function ($value) {
66
+ return ($value instanceof Model || $value instanceof self) ?
67
+ $value->toArray() :
68
+ $value;
69
+ }, $this->items);
70
+ }
71
+
72
+ /**
73
+ * 获取全部的数据
74
+ * @access public
75
+ * @return array
76
+ */
77
+ public function all()
78
+ {
79
+ return $this->items;
80
+ }
81
+
82
+ /**
83
+ * 交换数组中的键和值
84
+ * @access public
85
+ * @return static
86
+ */
87
+ public function flip()
88
+ {
89
+ return new static(array_flip($this->items));
90
+ }
91
+
92
+ /**
93
+ * 返回数组中所有的键名组成的新 Collection 实例
94
+ * @access public
95
+ * @return static
96
+ */
97
+ public function keys()
98
+ {
99
+ return new static(array_keys($this->items));
100
+ }
101
+
102
+ /**
103
+ * 返回数组中所有的值组成的新 Collection 实例
104
+ * @access public
105
+ * @return static
106
+ */
107
+ public function values()
108
+ {
109
+ return new static(array_values($this->items));
110
+ }
111
+
112
+ /**
113
+ * 合并数组并返回一个新的 Collection 实例
114
+ * @access public
115
+ * @param mixed $items 新的数据
116
+ * @return static
117
+ */
118
+ public function merge($items)
119
+ {
120
+ return new static(array_merge($this->items, $this->convertToArray($items)));
121
+ }
122
+
123
+ /**
124
+ * 比较数组,返回差集生成的新 Collection 实例
125
+ * @access public
126
+ * @param mixed $items 做比较的数据
127
+ * @return static
128
+ */
129
+ public function diff($items)
130
+ {
131
+ return new static(array_diff($this->items, $this->convertToArray($items)));
132
+ }
133
+
134
+ /**
135
+ * 比较数组,返回交集组成的 Collection 新实例
136
+ * @access public
137
+ * @param mixed $items 比较数据
138
+ * @return static
139
+ */
140
+ public function intersect($items)
141
+ {
142
+ return new static(array_intersect($this->items, $this->convertToArray($items)));
143
+ }
144
+
145
+ /**
146
+ * 返回并删除数据中的的最后一个元素(出栈)
147
+ * @access public
148
+ * @return mixed
149
+ */
150
+ public function pop()
151
+ {
152
+ return array_pop($this->items);
153
+ }
154
+
155
+ /**
156
+ * 返回并删除数据中首个元素
157
+ * @access public
158
+ * @return mixed
159
+ */
160
+ public function shift()
161
+ {
162
+ return array_shift($this->items);
163
+ }
164
+
165
+ /**
166
+ * 在数组开头插入一个元素
167
+ * @access public
168
+ * @param mixed $value 值
169
+ * @param mixed $key 键名
170
+ * @return void
171
+ */
172
+ public function unshift($value, $key = null)
173
+ {
174
+ if (is_null($key)) {
175
+ array_unshift($this->items, $value);
176
+ } else {
177
+ $this->items = [$key => $value] + $this->items;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * 在数组结尾插入一个元素
183
+ * @access public
184
+ * @param mixed $value 值
185
+ * @param mixed $key 键名
186
+ * @return void
187
+ */
188
+ public function push($value, $key = null)
189
+ {
190
+ if (is_null($key)) {
191
+ $this->items[] = $value;
192
+ } else {
193
+ $this->items[$key] = $value;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * 通过使用用户自定义函数,以字符串返回数组
199
+ * @access public
200
+ * @param callable $callback 回调函数
201
+ * @param mixed $initial 初始值
202
+ * @return mixed
203
+ */
204
+ public function reduce(callable $callback, $initial = null)
205
+ {
206
+ return array_reduce($this->items, $callback, $initial);
207
+ }
208
+
209
+ /**
210
+ * 以相反的顺序创建一个新的 Collection 实例
211
+ * @access public
212
+ * @return static
213
+ */
214
+ public function reverse()
215
+ {
216
+ return new static(array_reverse($this->items));
217
+ }
218
+
219
+ /**
220
+ * 把数据分割为新的数组块
221
+ * @access public
222
+ * @param int $size 分隔长度
223
+ * @param bool $preserveKeys 是否保持原数据索引
224
+ * @return static
225
+ */
226
+ public function chunk($size, $preserveKeys = false)
227
+ {
228
+ $chunks = [];
229
+
230
+ foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) {
231
+ $chunks[] = new static($chunk);
232
+ }
233
+
234
+ return new static($chunks);
235
+ }
236
+
237
+ /**
238
+ * 给数据中的每个元素执行回调
239
+ * @access public
240
+ * @param callable $callback 回调函数
241
+ * @return $this
242
+ */
243
+ public function each(callable $callback)
244
+ {
245
+ foreach ($this->items as $key => $item) {
246
+ $result = $callback($item, $key);
247
+
248
+ if (false === $result) {
249
+ break;
250
+ }
251
+
252
+ if (!is_object($item)) {
253
+ $this->items[$key] = $result;
254
+ }
255
+ }
256
+
257
+ return $this;
258
+ }
259
+
260
+ /**
261
+ * 用回调函数过滤数据中的元素
262
+ * @access public
263
+ * @param callable|null $callback 回调函数
264
+ * @return static
265
+ */
266
+ public function filter(callable $callback = null)
267
+ {
268
+ return new static(array_filter($this->items, $callback ?: null));
269
+ }
270
+
271
+ /**
272
+ * 返回数据中指定的一列
273
+ * @access public
274
+ * @param mixed $columnKey 键名
275
+ * @param null $indexKey 作为索引值的列
276
+ * @return array
277
+ */
278
+ public function column($columnKey, $indexKey = null)
279
+ {
280
+ if (function_exists('array_column')) {
281
+ return array_column($this->items, $columnKey, $indexKey);
282
+ }
283
+
284
+ $result = [];
285
+ foreach ($this->items as $row) {
286
+ $key = $value = null;
287
+ $keySet = $valueSet = false;
288
+
289
+ if (null !== $indexKey && array_key_exists($indexKey, $row)) {
290
+ $key = (string) $row[$indexKey];
291
+ $keySet = true;
292
+ }
293
+
294
+ if (null === $columnKey) {
295
+ $valueSet = true;
296
+ $value = $row;
297
+ } elseif (is_array($row) && array_key_exists($columnKey, $row)) {
298
+ $valueSet = true;
299
+ $value = $row[$columnKey];
300
+ }
301
+
302
+ if ($valueSet) {
303
+ if ($keySet) {
304
+ $result[$key] = $value;
305
+ } else {
306
+ $result[] = $value;
307
+ }
308
+ }
309
+ }
310
+
311
+ return $result;
312
+ }
313
+
314
+ /**
315
+ * 对数据排序,并返回排序后的数据组成的新 Collection 实例
316
+ * @access public
317
+ * @param callable|null $callback 回调函数
318
+ * @return static
319
+ */
320
+ public function sort(callable $callback = null)
321
+ {
322
+ $items = $this->items;
323
+ $callback = $callback ?: function ($a, $b) {
324
+ return $a == $b ? 0 : (($a < $b) ? -1 : 1);
325
+ };
326
+
327
+ uasort($items, $callback);
328
+ return new static($items);
329
+ }
330
+
331
+ /**
332
+ * 将数据打乱后组成新的 Collection 实例
333
+ * @access public
334
+ * @return static
335
+ */
336
+ public function shuffle()
337
+ {
338
+ $items = $this->items;
339
+
340
+ shuffle($items);
341
+ return new static($items);
342
+ }
343
+
344
+ /**
345
+ * 截取数据并返回新的 Collection 实例
346
+ * @access public
347
+ * @param int $offset 起始位置
348
+ * @param int $length 截取长度
349
+ * @param bool $preserveKeys 是否保持原先的键名
350
+ * @return static
351
+ */
352
+ public function slice($offset, $length = null, $preserveKeys = false)
353
+ {
354
+ return new static(array_slice($this->items, $offset, $length, $preserveKeys));
355
+ }
356
+
357
+ /**
358
+ * 指定的键是否存在
359
+ * @access public
360
+ * @param mixed $offset 键名
361
+ * @return bool
362
+ */
363
+ public function offsetExists($offset)
364
+ {
365
+ return array_key_exists($offset, $this->items);
366
+ }
367
+
368
+ /**
369
+ * 获取指定键对应的值
370
+ * @access public
371
+ * @param mixed $offset 键名
372
+ * @return mixed
373
+ */
374
+ public function offsetGet($offset)
375
+ {
376
+ return $this->items[$offset];
377
+ }
378
+
379
+ /**
380
+ * 设置键值
381
+ * @access public
382
+ * @param mixed $offset 键名
383
+ * @param mixed $value 值
384
+ * @return void
385
+ */
386
+ public function offsetSet($offset, $value)
387
+ {
388
+ if (is_null($offset)) {
389
+ $this->items[] = $value;
390
+ } else {
391
+ $this->items[$offset] = $value;
392
+ }
393
+ }
394
+
395
+ /**
396
+ * 删除指定键值
397
+ * @access public
398
+ * @param mixed $offset 键名
399
+ * @return void
400
+ */
401
+ public function offsetUnset($offset)
402
+ {
403
+ unset($this->items[$offset]);
404
+ }
405
+
406
+ /**
407
+ * 统计数据的个数
408
+ * @access public
409
+ * @return int
410
+ */
411
+ public function count()
412
+ {
413
+ return count($this->items);
414
+ }
415
+
416
+ /**
417
+ * 获取数据的迭代器
418
+ * @access public
419
+ * @return ArrayIterator
420
+ */
421
+ public function getIterator()
422
+ {
423
+ return new ArrayIterator($this->items);
424
+ }
425
+
426
+ /**
427
+ * 将数据反序列化成数组
428
+ * @access public
429
+ * @return array
430
+ */
431
+ public function jsonSerialize()
432
+ {
433
+ return $this->toArray();
434
+ }
435
+
436
+ /**
437
+ * 转换当前数据集为 JSON 字符串
438
+ * @access public
439
+ * @param integer $options json 参数
440
+ * @return string
441
+ */
442
+ public function toJson($options = JSON_UNESCAPED_UNICODE)
443
+ {
444
+ return json_encode($this->toArray(), $options);
445
+ }
446
+
447
+ /**
448
+ * 将数据转换成字符串
449
+ * @access public
450
+ * @return string
451
+ */
452
+ public function __toString()
453
+ {
454
+ return $this->toJson();
455
+ }
456
+
457
+ /**
458
+ * 将数据转换成数组
459
+ * @access protected
460
+ * @param mixed $items 数据
461
+ * @return array
462
+ */
463
+ protected function convertToArray($items)
464
+ {
465
+ return $items instanceof self ? $items->all() : (array) $items;
466
+ }
467
+ }
thinkphp/library/think/Config.php ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Config
15
+ {
16
+ /**
17
+ * @var array 配置参数
18
+ */
19
+ private static $config = [];
20
+
21
+ /**
22
+ * @var string 参数作用域
23
+ */
24
+ private static $range = '_sys_';
25
+
26
+ /**
27
+ * 设定配置参数的作用域
28
+ * @access public
29
+ * @param string $range 作用域
30
+ * @return void
31
+ */
32
+ public static function range($range)
33
+ {
34
+ self::$range = $range;
35
+
36
+ if (!isset(self::$config[$range])) self::$config[$range] = [];
37
+ }
38
+
39
+ /**
40
+ * 解析配置文件或内容
41
+ * @access public
42
+ * @param string $config 配置文件路径或内容
43
+ * @param string $type 配置解析类型
44
+ * @param string $name 配置名(如设置即表示二级配置)
45
+ * @param string $range 作用域
46
+ * @return mixed
47
+ */
48
+ public static function parse($config, $type = '', $name = '', $range = '')
49
+ {
50
+ $range = $range ?: self::$range;
51
+
52
+ if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION);
53
+
54
+ $class = false !== strpos($type, '\\') ?
55
+ $type :
56
+ '\\think\\config\\driver\\' . ucwords($type);
57
+
58
+ return self::set((new $class())->parse($config), $name, $range);
59
+ }
60
+
61
+ /**
62
+ * 加载配置文件(PHP格式)
63
+ * @access public
64
+ * @param string $file 配置文件名
65
+ * @param string $name 配置名(如设置即表示二级配置)
66
+ * @param string $range 作用域
67
+ * @return mixed
68
+ */
69
+ public static function load($file, $name = '', $range = '')
70
+ {
71
+ $range = $range ?: self::$range;
72
+
73
+ if (!isset(self::$config[$range])) self::$config[$range] = [];
74
+
75
+ if (is_file($file)) {
76
+ $name = strtolower($name);
77
+ $type = pathinfo($file, PATHINFO_EXTENSION);
78
+
79
+ if ('php' == $type) {
80
+ return self::set(include $file, $name, $range);
81
+ }
82
+
83
+ if ('yaml' == $type && function_exists('yaml_parse_file')) {
84
+ return self::set(yaml_parse_file($file), $name, $range);
85
+ }
86
+
87
+ return self::parse($file, $type, $name, $range);
88
+ }
89
+
90
+ return self::$config[$range];
91
+ }
92
+
93
+ /**
94
+ * 检测配置是否存在
95
+ * @access public
96
+ * @param string $name 配置参数名(支持二级配置 . 号分割)
97
+ * @param string $range 作用域
98
+ * @return bool
99
+ */
100
+ public static function has($name, $range = '')
101
+ {
102
+ $range = $range ?: self::$range;
103
+
104
+ if (!strpos($name, '.')) {
105
+ return isset(self::$config[$range][strtolower($name)]);
106
+ }
107
+
108
+ // 二维数组设置和获取支持
109
+ $name = explode('.', $name, 2);
110
+ return isset(self::$config[$range][strtolower($name[0])][$name[1]]);
111
+ }
112
+
113
+ /**
114
+ * 获取配置参数 为空则获取所有配置
115
+ * @access public
116
+ * @param string $name 配置参数名(支持二级配置 . 号分割)
117
+ * @param string $range 作用域
118
+ * @return mixed
119
+ */
120
+ public static function get($name = null, $range = '')
121
+ {
122
+ $range = $range ?: self::$range;
123
+
124
+ // 无参数时获取所有
125
+ if (empty($name) && isset(self::$config[$range])) {
126
+ return self::$config[$range];
127
+ }
128
+
129
+ // 非二级配置时直接返回
130
+ if (!strpos($name, '.')) {
131
+ $name = strtolower($name);
132
+ return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null;
133
+ }
134
+
135
+ // 二维数组设置和获取支持
136
+ $name = explode('.', $name, 2);
137
+ $name[0] = strtolower($name[0]);
138
+
139
+ if (!isset(self::$config[$range][$name[0]])) {
140
+ // 动态载入额外配置
141
+ $module = Request::instance()->module();
142
+ $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT;
143
+
144
+ is_file($file) && self::load($file, $name[0]);
145
+ }
146
+
147
+ return isset(self::$config[$range][$name[0]][$name[1]]) ?
148
+ self::$config[$range][$name[0]][$name[1]] :
149
+ null;
150
+ }
151
+
152
+ /**
153
+ * 设置配置参数 name 为数组则为批量设置
154
+ * @access public
155
+ * @param string|array $name 配置参数名(支持二级配置 . 号分割)
156
+ * @param mixed $value 配置值
157
+ * @param string $range 作用域
158
+ * @return mixed
159
+ */
160
+ public static function set($name, $value = null, $range = '')
161
+ {
162
+ $range = $range ?: self::$range;
163
+
164
+ if (!isset(self::$config[$range])) self::$config[$range] = [];
165
+
166
+ // 字符串则表示单个配置设置
167
+ if (is_string($name)) {
168
+ if (!strpos($name, '.')) {
169
+ self::$config[$range][strtolower($name)] = $value;
170
+ } else {
171
+ // 二维数组
172
+ $name = explode('.', $name, 2);
173
+ self::$config[$range][strtolower($name[0])][$name[1]] = $value;
174
+ }
175
+
176
+ return $value;
177
+ }
178
+
179
+ // 数组则表示批量设置
180
+ if (is_array($name)) {
181
+ if (!empty($value)) {
182
+ self::$config[$range][$value] = isset(self::$config[$range][$value]) ?
183
+ array_merge(self::$config[$range][$value], $name) :
184
+ $name;
185
+
186
+ return self::$config[$range][$value];
187
+ }
188
+
189
+ return self::$config[$range] = array_merge(
190
+ self::$config[$range], array_change_key_case($name)
191
+ );
192
+ }
193
+
194
+ // 为空直接返回已有配置
195
+ return self::$config[$range];
196
+ }
197
+
198
+ /**
199
+ * 重置配置参数
200
+ * @access public
201
+ * @param string $range 作用域
202
+ * @return void
203
+ */
204
+ public static function reset($range = '')
205
+ {
206
+ $range = $range ?: self::$range;
207
+
208
+ if (true === $range) {
209
+ self::$config = [];
210
+ } else {
211
+ self::$config[$range] = [];
212
+ }
213
+ }
214
+ }
thinkphp/library/think/Console.php ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // +----------------------------------------------------------------------
3
+ // | TopThink [ WE CAN DO IT JUST THINK IT ]
4
+ // +----------------------------------------------------------------------
5
+ // | Copyright (c) 2015 http://www.topthink.com All rights reserved.
6
+ // +----------------------------------------------------------------------
7
+ // | Author: zhangyajun <448901948@qq.com>
8
+ // +----------------------------------------------------------------------
9
+
10
+ namespace think;
11
+
12
+ use think\console\Command;
13
+ use think\console\command\Help as HelpCommand;
14
+ use think\console\Input;
15
+ use think\console\input\Argument as InputArgument;
16
+ use think\console\input\Definition as InputDefinition;
17
+ use think\console\input\Option as InputOption;
18
+ use think\console\Output;
19
+ use think\console\output\driver\Buffer;
20
+
21
+ class Console
22
+ {
23
+ /**
24
+ * @var string 命令名称
25
+ */
26
+ private $name;
27
+
28
+ /**
29
+ * @var string 命令版本
30
+ */
31
+ private $version;
32
+
33
+ /**
34
+ * @var Command[] 命令
35
+ */
36
+ private $commands = [];
37
+
38
+ /**
39
+ * @var bool 是否需要帮助信息
40
+ */
41
+ private $wantHelps = false;
42
+
43
+ /**
44
+ * @var bool 是否捕获异常
45
+ */
46
+ private $catchExceptions = true;
47
+
48
+ /**
49
+ * @var bool 是否自动退出执行
50
+ */
51
+ private $autoExit = true;
52
+
53
+ /**
54
+ * @var InputDefinition 输入定义
55
+ */
56
+ private $definition;
57
+
58
+ /**
59
+ * @var string 默认执行的命令
60
+ */
61
+ private $defaultCommand;
62
+
63
+ /**
64
+ * @var array 默认提供的命令
65
+ */
66
+ private static $defaultCommands = [
67
+ "think\\console\\command\\Help",
68
+ "think\\console\\command\\Lists",
69
+ "think\\console\\command\\Build",
70
+ "think\\console\\command\\Clear",
71
+ "think\\console\\command\\make\\Controller",
72
+ "think\\console\\command\\make\\Model",
73
+ "think\\console\\command\\optimize\\Autoload",
74
+ "think\\console\\command\\optimize\\Config",
75
+ "think\\console\\command\\optimize\\Route",
76
+ "think\\console\\command\\optimize\\Schema",
77
+ ];
78
+
79
+ /**
80
+ * Console constructor.
81
+ * @access public
82
+ * @param string $name 名称
83
+ * @param string $version 版本
84
+ * @param null|string $user 执行用户
85
+ */
86
+ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null)
87
+ {
88
+ $this->name = $name;
89
+ $this->version = $version;
90
+
91
+ if ($user) {
92
+ $this->setUser($user);
93
+ }
94
+
95
+ $this->defaultCommand = 'list';
96
+ $this->definition = $this->getDefaultInputDefinition();
97
+
98
+ foreach ($this->getDefaultCommands() as $command) {
99
+ $this->add($command);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * 设置执行用户
105
+ * @param $user
106
+ */
107
+ public function setUser($user)
108
+ {
109
+ $user = posix_getpwnam($user);
110
+ if ($user) {
111
+ posix_setuid($user['uid']);
112
+ posix_setgid($user['gid']);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 初始化 Console
118
+ * @access public
119
+ * @param bool $run 是否运行 Console
120
+ * @return int|Console
121
+ */
122
+ public static function init($run = true)
123
+ {
124
+ static $console;
125
+
126
+ if (!$console) {
127
+ $config = Config::get('console');
128
+ // 实例化 console
129
+ $console = new self($config['name'], $config['version'], $config['user']);
130
+
131
+ // 读取指令集
132
+ if (is_file(CONF_PATH . 'command' . EXT)) {
133
+ $commands = include CONF_PATH . 'command' . EXT;
134
+
135
+ if (is_array($commands)) {
136
+ foreach ($commands as $command) {
137
+ class_exists($command) &&
138
+ is_subclass_of($command, "\\think\\console\\Command") &&
139
+ $console->add(new $command()); // 注册指令
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ return $run ? $console->run() : $console;
146
+ }
147
+
148
+ /**
149
+ * 调用命令
150
+ * @access public
151
+ * @param string $command
152
+ * @param array $parameters
153
+ * @param string $driver
154
+ * @return Output
155
+ */
156
+ public static function call($command, array $parameters = [], $driver = 'buffer')
157
+ {
158
+ $console = self::init(false);
159
+
160
+ array_unshift($parameters, $command);
161
+
162
+ $input = new Input($parameters);
163
+ $output = new Output($driver);
164
+
165
+ $console->setCatchExceptions(false);
166
+ $console->find($command)->run($input, $output);
167
+
168
+ return $output;
169
+ }
170
+
171
+ /**
172
+ * 执行当前的指令
173
+ * @access public
174
+ * @return int
175
+ * @throws \Exception
176
+ */
177
+ public function run()
178
+ {
179
+ $input = new Input();
180
+ $output = new Output();
181
+
182
+ $this->configureIO($input, $output);
183
+
184
+ try {
185
+ $exitCode = $this->doRun($input, $output);
186
+ } catch (\Exception $e) {
187
+ if (!$this->catchExceptions) throw $e;
188
+
189
+ $output->renderException($e);
190
+
191
+ $exitCode = $e->getCode();
192
+
193
+ if (is_numeric($exitCode)) {
194
+ $exitCode = ((int) $exitCode) ?: 1;
195
+ } else {
196
+ $exitCode = 1;
197
+ }
198
+ }
199
+
200
+ if ($this->autoExit) {
201
+ if ($exitCode > 255) $exitCode = 255;
202
+
203
+ exit($exitCode);
204
+ }
205
+
206
+ return $exitCode;
207
+ }
208
+
209
+ /**
210
+ * 执行指令
211
+ * @access public
212
+ * @param Input $input 输入
213
+ * @param Output $output 输出
214
+ * @return int
215
+ */
216
+ public function doRun(Input $input, Output $output)
217
+ {
218
+ // 获取版本信息
219
+ if (true === $input->hasParameterOption(['--version', '-V'])) {
220
+ $output->writeln($this->getLongVersion());
221
+
222
+ return 0;
223
+ }
224
+
225
+ $name = $this->getCommandName($input);
226
+
227
+ // 获取帮助信息
228
+ if (true === $input->hasParameterOption(['--help', '-h'])) {
229
+ if (!$name) {
230
+ $name = 'help';
231
+ $input = new Input(['help']);
232
+ } else {
233
+ $this->wantHelps = true;
234
+ }
235
+ }
236
+
237
+ if (!$name) {
238
+ $name = $this->defaultCommand;
239
+ $input = new Input([$this->defaultCommand]);
240
+ }
241
+
242
+ return $this->doRunCommand($this->find($name), $input, $output);
243
+ }
244
+
245
+ /**
246
+ * 设置输入参数定义
247
+ * @access public
248
+ * @param InputDefinition $definition 输入定义
249
+ * @return $this;
250
+ */
251
+ public function setDefinition(InputDefinition $definition)
252
+ {
253
+ $this->definition = $definition;
254
+
255
+ return $this;
256
+ }
257
+
258
+ /**
259
+ * 获取输入参数定义
260
+ * @access public
261
+ * @return InputDefinition
262
+ */
263
+ public function getDefinition()
264
+ {
265
+ return $this->definition;
266
+ }
267
+
268
+ /**
269
+ * 获取帮助信息
270
+ * @access public
271
+ * @return string
272
+ */
273
+ public function getHelp()
274
+ {
275
+ return $this->getLongVersion();
276
+ }
277
+
278
+ /**
279
+ * 设置是否捕获异常
280
+ * @access public
281
+ * @param bool $boolean 是否捕获
282
+ * @return $this
283
+ */
284
+ public function setCatchExceptions($boolean)
285
+ {
286
+ $this->catchExceptions = (bool) $boolean;
287
+
288
+ return $this;
289
+ }
290
+
291
+ /**
292
+ * 设置是否自动退出
293
+ * @access public
294
+ * @param bool $boolean 是否自动退出
295
+ * @return $this
296
+ */
297
+ public function setAutoExit($boolean)
298
+ {
299
+ $this->autoExit = (bool) $boolean;
300
+
301
+ return $this;
302
+ }
303
+
304
+ /**
305
+ * 获取名称
306
+ * @access public
307
+ * @return string
308
+ */
309
+ public function getName()
310
+ {
311
+ return $this->name;
312
+ }
313
+
314
+ /**
315
+ * 设置名称
316
+ * @access public
317
+ * @param string $name 名称
318
+ * @return $this
319
+ */
320
+ public function setName($name)
321
+ {
322
+ $this->name = $name;
323
+
324
+ return $this;
325
+ }
326
+
327
+ /**
328
+ * 获取版本
329
+ * @access public
330
+ * @return string
331
+ */
332
+ public function getVersion()
333
+ {
334
+ return $this->version;
335
+ }
336
+
337
+ /**
338
+ * 设置版本
339
+ * @access public
340
+ * @param string $version 版本信息
341
+ * @return $this
342
+ */
343
+ public function setVersion($version)
344
+ {
345
+ $this->version = $version;
346
+
347
+ return $this;
348
+ }
349
+
350
+ /**
351
+ * 获取完整的版本号
352
+ * @access public
353
+ * @return string
354
+ */
355
+ public function getLongVersion()
356
+ {
357
+ if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
358
+ return sprintf(
359
+ '<info>%s</info> version <comment>%s</comment>',
360
+ $this->getName(),
361
+ $this->getVersion()
362
+ );
363
+ }
364
+
365
+ return '<info>Console Tool</info>';
366
+ }
367
+
368
+ /**
369
+ * 注册一个指令
370
+ * @access public
371
+ * @param string $name 指令名称
372
+ * @return Command
373
+ */
374
+ public function register($name)
375
+ {
376
+ return $this->add(new Command($name));
377
+ }
378
+
379
+ /**
380
+ * 批量添加指令
381
+ * @access public
382
+ * @param Command[] $commands 指令实例
383
+ * @return $this
384
+ */
385
+ public function addCommands(array $commands)
386
+ {
387
+ foreach ($commands as $command) $this->add($command);
388
+
389
+ return $this;
390
+ }
391
+
392
+ /**
393
+ * 添加一个指令
394
+ * @access public
395
+ * @param Command $command 命令实例
396
+ * @return Command|bool
397
+ */
398
+ public function add(Command $command)
399
+ {
400
+ if (!$command->isEnabled()) {
401
+ $command->setConsole(null);
402
+ return false;
403
+ }
404
+
405
+ $command->setConsole($this);
406
+
407
+ if (null === $command->getDefinition()) {
408
+ throw new \LogicException(
409
+ sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))
410
+ );
411
+ }
412
+
413
+ $this->commands[$command->getName()] = $command;
414
+
415
+ foreach ($command->getAliases() as $alias) {
416
+ $this->commands[$alias] = $command;
417
+ }
418
+
419
+ return $command;
420
+ }
421
+
422
+ /**
423
+ * 获取指令
424
+ * @access public
425
+ * @param string $name 指令名称
426
+ * @return Command
427
+ * @throws \InvalidArgumentException
428
+ */
429
+ public function get($name)
430
+ {
431
+ if (!isset($this->commands[$name])) {
432
+ throw new \InvalidArgumentException(
433
+ sprintf('The command "%s" does not exist.', $name)
434
+ );
435
+ }
436
+
437
+ $command = $this->commands[$name];
438
+
439
+ if ($this->wantHelps) {
440
+ $this->wantHelps = false;
441
+
442
+ /** @var HelpCommand $helpCommand */
443
+ $helpCommand = $this->get('help');
444
+ $helpCommand->setCommand($command);
445
+
446
+ return $helpCommand;
447
+ }
448
+
449
+ return $command;
450
+ }
451
+
452
+ /**
453
+ * 某个指令是否存在
454
+ * @access public
455
+ * @param string $name 指令名称
456
+ * @return bool
457
+ */
458
+ public function has($name)
459
+ {
460
+ return isset($this->commands[$name]);
461
+ }
462
+
463
+ /**
464
+ * 获取所有的命名空间
465
+ * @access public
466
+ * @return array
467
+ */
468
+ public function getNamespaces()
469
+ {
470
+ $namespaces = [];
471
+
472
+ foreach ($this->commands as $command) {
473
+ $namespaces = array_merge(
474
+ $namespaces, $this->extractAllNamespaces($command->getName())
475
+ );
476
+
477
+ foreach ($command->getAliases() as $alias) {
478
+ $namespaces = array_merge(
479
+ $namespaces, $this->extractAllNamespaces($alias)
480
+ );
481
+ }
482
+ }
483
+
484
+ return array_values(array_unique(array_filter($namespaces)));
485
+ }
486
+
487
+ /**
488
+ * 查找注册命名空间中的名称或缩写
489
+ * @access public
490
+ * @param string $namespace
491
+ * @return string
492
+ * @throws \InvalidArgumentException
493
+ */
494
+ public function findNamespace($namespace)
495
+ {
496
+ $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
497
+ return preg_quote($matches[1]) . '[^:]*';
498
+ }, $namespace);
499
+
500
+ $allNamespaces = $this->getNamespaces();
501
+ $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
502
+
503
+ if (empty($namespaces)) {
504
+ $message = sprintf(
505
+ 'There are no commands defined in the "%s" namespace.', $namespace
506
+ );
507
+
508
+ if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
509
+ if (1 == count($alternatives)) {
510
+ $message .= "\n\nDid you mean this?\n ";
511
+ } else {
512
+ $message .= "\n\nDid you mean one of these?\n ";
513
+ }
514
+
515
+ $message .= implode("\n ", $alternatives);
516
+ }
517
+
518
+ throw new \InvalidArgumentException($message);
519
+ }
520
+
521
+ $exact = in_array($namespace, $namespaces, true);
522
+
523
+ if (count($namespaces) > 1 && !$exact) {
524
+ throw new \InvalidArgumentException(
525
+ sprintf(
526
+ 'The namespace "%s" is ambiguous (%s).',
527
+ $namespace,
528
+ $this->getAbbreviationSuggestions(array_values($namespaces)))
529
+ );
530
+ }
531
+
532
+ return $exact ? $namespace : reset($namespaces);
533
+ }
534
+
535
+ /**
536
+ * 查找指令
537
+ * @access public
538
+ * @param string $name 名称或者别名
539
+ * @return Command
540
+ * @throws \InvalidArgumentException
541
+ */
542
+ public function find($name)
543
+ {
544
+ $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
545
+ return preg_quote($matches[1]) . '[^:]*';
546
+ }, $name);
547
+
548
+ $allCommands = array_keys($this->commands);
549
+ $commands = preg_grep('{^' . $expr . '}', $allCommands);
550
+
551
+ if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
552
+ if (false !== ($pos = strrpos($name, ':'))) {
553
+ $this->findNamespace(substr($name, 0, $pos));
554
+ }
555
+
556
+ $message = sprintf('Command "%s" is not defined.', $name);
557
+
558
+ if ($alternatives = $this->findAlternatives($name, $allCommands)) {
559
+ if (1 == count($alternatives)) {
560
+ $message .= "\n\nDid you mean this?\n ";
561
+ } else {
562
+ $message .= "\n\nDid you mean one of these?\n ";
563
+ }
564
+ $message .= implode("\n ", $alternatives);
565
+ }
566
+
567
+ throw new \InvalidArgumentException($message);
568
+ }
569
+
570
+ if (count($commands) > 1) {
571
+ $commandList = $this->commands;
572
+ $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
573
+ $commandName = $commandList[$nameOrAlias]->getName();
574
+
575
+ return $commandName === $nameOrAlias || !in_array($commandName, $commands);
576
+ });
577
+ }
578
+
579
+ $exact = in_array($name, $commands, true);
580
+ if (count($commands) > 1 && !$exact) {
581
+ $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
582
+
583
+ throw new \InvalidArgumentException(
584
+ sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)
585
+ );
586
+ }
587
+
588
+ return $this->get($exact ? $name : reset($commands));
589
+ }
590
+
591
+ /**
592
+ * 获取所有的指令
593
+ * @access public
594
+ * @param string $namespace 命名空间
595
+ * @return Command[]
596
+ */
597
+ public function all($namespace = null)
598
+ {
599
+ if (null === $namespace) return $this->commands;
600
+
601
+ $commands = [];
602
+
603
+ foreach ($this->commands as $name => $command) {
604
+ $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1);
605
+
606
+ if ($ext === $namespace) $commands[$name] = $command;
607
+ }
608
+
609
+ return $commands;
610
+ }
611
+
612
+ /**
613
+ * 获取可能的指令名
614
+ * @access public
615
+ * @param array $names 指令名
616
+ * @return array
617
+ */
618
+ public static function getAbbreviations($names)
619
+ {
620
+ $abbrevs = [];
621
+ foreach ($names as $name) {
622
+ for ($len = strlen($name); $len > 0; --$len) {
623
+ $abbrev = substr($name, 0, $len);
624
+ $abbrevs[$abbrev][] = $name;
625
+ }
626
+ }
627
+
628
+ return $abbrevs;
629
+ }
630
+
631
+ /**
632
+ * 配置基于用户的参数和选项的输入和输出实例
633
+ * @access protected
634
+ * @param Input $input 输入实例
635
+ * @param Output $output 输出实例
636
+ * @return void
637
+ */
638
+ protected function configureIO(Input $input, Output $output)
639
+ {
640
+ if (true === $input->hasParameterOption(['--ansi'])) {
641
+ $output->setDecorated(true);
642
+ } elseif (true === $input->hasParameterOption(['--no-ansi'])) {
643
+ $output->setDecorated(false);
644
+ }
645
+
646
+ if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
647
+ $input->setInteractive(false);
648
+ }
649
+
650
+ if (true === $input->hasParameterOption(['--quiet', '-q'])) {
651
+ $output->setVerbosity(Output::VERBOSITY_QUIET);
652
+ } else {
653
+ if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
654
+ $output->setVerbosity(Output::VERBOSITY_DEBUG);
655
+ } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
656
+ $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
657
+ } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
658
+ $output->setVerbosity(Output::VERBOSITY_VERBOSE);
659
+ }
660
+ }
661
+ }
662
+
663
+ /**
664
+ * 执行指令
665
+ * @access protected
666
+ * @param Command $command 指令实例
667
+ * @param Input $input 输入实例
668
+ * @param Output $output 输出实例
669
+ * @return int
670
+ * @throws \Exception
671
+ */
672
+ protected function doRunCommand(Command $command, Input $input, Output $output)
673
+ {
674
+ return $command->run($input, $output);
675
+ }
676
+
677
+ /**
678
+ * 获取指令的名称
679
+ * @access protected
680
+ * @param Input $input 输入实例
681
+ * @return string
682
+ */
683
+ protected function getCommandName(Input $input)
684
+ {
685
+ return $input->getFirstArgument();
686
+ }
687
+
688
+ /**
689
+ * 获取默认输入定义
690
+ * @access protected
691
+ * @return InputDefinition
692
+ */
693
+ protected function getDefaultInputDefinition()
694
+ {
695
+ return new InputDefinition([
696
+ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
697
+ new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
698
+ new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
699
+ new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
700
+ new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
701
+ new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
702
+ new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
703
+ new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
704
+ ]);
705
+ }
706
+
707
+ /**
708
+ * 获取默认命令
709
+ * @access protected
710
+ * @return Command[]
711
+ */
712
+ protected function getDefaultCommands()
713
+ {
714
+ $defaultCommands = [];
715
+
716
+ foreach (self::$defaultCommands as $class) {
717
+ if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) {
718
+ $defaultCommands[] = new $class();
719
+ }
720
+ }
721
+
722
+ return $defaultCommands;
723
+ }
724
+
725
+ /**
726
+ * 添加默认指令
727
+ * @access public
728
+ * @param array $classes 指令
729
+ * @return void
730
+ */
731
+ public static function addDefaultCommands(array $classes)
732
+ {
733
+ self::$defaultCommands = array_merge(self::$defaultCommands, $classes);
734
+ }
735
+
736
+ /**
737
+ * 获取可能的建议
738
+ * @access private
739
+ * @param array $abbrevs
740
+ * @return string
741
+ */
742
+ private function getAbbreviationSuggestions($abbrevs)
743
+ {
744
+ return sprintf(
745
+ '%s, %s%s',
746
+ $abbrevs[0],
747
+ $abbrevs[1],
748
+ count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''
749
+ );
750
+ }
751
+
752
+ /**
753
+ * 返回指令的命名空间部分
754
+ * @access public
755
+ * @param string $name 指令名称
756
+ * @param string $limit 部分的命名空间的最大数量
757
+ * @return string
758
+ */
759
+ public function extractNamespace($name, $limit = null)
760
+ {
761
+ $parts = explode(':', $name);
762
+ array_pop($parts);
763
+
764
+ return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
765
+ }
766
+
767
+ /**
768
+ * 查找可替代的建议
769
+ * @access private
770
+ * @param string $name 指令名称
771
+ * @param array|\Traversable $collection 建议集合
772
+ * @return array
773
+ */
774
+ private function findAlternatives($name, $collection)
775
+ {
776
+ $threshold = 1e3;
777
+ $alternatives = [];
778
+ $collectionParts = [];
779
+
780
+ foreach ($collection as $item) {
781
+ $collectionParts[$item] = explode(':', $item);
782
+ }
783
+
784
+ foreach (explode(':', $name) as $i => $subname) {
785
+ foreach ($collectionParts as $collectionName => $parts) {
786
+ $exists = isset($alternatives[$collectionName]);
787
+
788
+ if (!isset($parts[$i]) && $exists) {
789
+ $alternatives[$collectionName] += $threshold;
790
+ continue;
791
+ } elseif (!isset($parts[$i])) {
792
+ continue;
793
+ }
794
+
795
+ $lev = levenshtein($subname, $parts[$i]);
796
+
797
+ if ($lev <= strlen($subname) / 3 ||
798
+ '' !== $subname &&
799
+ false !== strpos($parts[$i], $subname)
800
+ ) {
801
+ $alternatives[$collectionName] = $exists ?
802
+ $alternatives[$collectionName] + $lev :
803
+ $lev;
804
+ } elseif ($exists) {
805
+ $alternatives[$collectionName] += $threshold;
806
+ }
807
+ }
808
+ }
809
+
810
+ foreach ($collection as $item) {
811
+ $lev = levenshtein($name, $item);
812
+
813
+ if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
814
+ $alternatives[$item] = isset($alternatives[$item]) ?
815
+ $alternatives[$item] - $lev :
816
+ $lev;
817
+ }
818
+ }
819
+
820
+ $alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
821
+ return $lev < 2 * $threshold;
822
+ });
823
+
824
+ asort($alternatives);
825
+
826
+ return array_keys($alternatives);
827
+ }
828
+
829
+ /**
830
+ * 设置默认的指令
831
+ * @access public
832
+ * @param string $commandName 指令名称
833
+ * @return $this
834
+ */
835
+ public function setDefaultCommand($commandName)
836
+ {
837
+ $this->defaultCommand = $commandName;
838
+
839
+ return $this;
840
+ }
841
+
842
+ /**
843
+ * 返回所有的命名空间
844
+ * @access private
845
+ * @param string $name 指令名称
846
+ * @return array
847
+ */
848
+ private function extractAllNamespaces($name)
849
+ {
850
+ $namespaces = [];
851
+
852
+ foreach (explode(':', $name, -1) as $part) {
853
+ if (count($namespaces)) {
854
+ $namespaces[] = end($namespaces) . ':' . $part;
855
+ } else {
856
+ $namespaces[] = $part;
857
+ }
858
+ }
859
+
860
+ return $namespaces;
861
+ }
862
+
863
+ }
thinkphp/library/think/Controller.php ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\ValidateException;
15
+ use traits\controller\Jump;
16
+ use think\Log;
17
+
18
+ Loader::import('controller/Jump', TRAIT_PATH, EXT);
19
+
20
+ class Controller
21
+ {
22
+ use Jump;
23
+
24
+ /**
25
+ * @var \think\View 视图类实例
26
+ */
27
+ protected $view;
28
+
29
+ /**
30
+ * @var \think\Request Request 实例
31
+ */
32
+ protected $request;
33
+
34
+ /**
35
+ * @var bool 验证失败是否抛出异常
36
+ */
37
+ protected $failException = false;
38
+
39
+ /**
40
+ * @var bool 是否批量验证
41
+ */
42
+ protected $batchValidate = false;
43
+
44
+ /**
45
+ * @var array 前置操作方法列表
46
+ */
47
+ protected $beforeActionList = [];
48
+
49
+ /**
50
+ * 构造方法
51
+ * @access public
52
+ * @param Request $request Request 对象
53
+ */
54
+ public function __construct(Request $request = null)
55
+ {
56
+ $this->view = View::instance(Config::get('template'), Config::get('view_replace_str'));
57
+ $this->request = is_null($request) ? Request::instance() : $request;
58
+
59
+ // 控制器初始化
60
+ $this->_initialize();
61
+
62
+ // 前置操作方法
63
+ if ($this->beforeActionList) {
64
+ foreach ($this->beforeActionList as $method => $options) {
65
+ is_numeric($method) ?
66
+ $this->beforeAction($options) :
67
+ $this->beforeAction($method, $options);
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 初始化操作
74
+ * @access protected
75
+ */
76
+ protected function _initialize()
77
+ {
78
+ }
79
+
80
+ /**
81
+ * 前置操作
82
+ * @access protected
83
+ * @param string $method 前置操作方法名
84
+ * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]]
85
+ * @return void
86
+ */
87
+ protected function beforeAction($method, $options = [])
88
+ {
89
+ if (isset($options['only'])) {
90
+ if (is_string($options['only'])) {
91
+ $options['only'] = explode(',', $options['only']);
92
+ }
93
+
94
+ if (!in_array($this->request->action(), $options['only'])) {
95
+ return;
96
+ }
97
+ } elseif (isset($options['except'])) {
98
+ if (is_string($options['except'])) {
99
+ $options['except'] = explode(',', $options['except']);
100
+ }
101
+
102
+ if (in_array($this->request->action(), $options['except'])) {
103
+ return;
104
+ }
105
+ }
106
+
107
+ call_user_func([$this, $method]);
108
+ }
109
+
110
+ /**
111
+ * 加载模板输出
112
+ * @access protected
113
+ * @param string $template 模板文件名
114
+ * @param array $vars 模板输出变量
115
+ * @param array $replace 模板替换
116
+ * @param array $config 模板参数
117
+ * @return mixed
118
+ */
119
+ protected function fetch($template = '', $vars = [], $replace = [], $config = [])
120
+ {
121
+ // 如果是新版本
122
+ if($GLOBALS['config']['site']['new_version'] == 1 || !isset($GLOBALS['config']['site']['new_version']) || (empty($GLOBALS['config']['site']['new_version']) && $GLOBALS['config']['site']['new_version'] != 0)){
123
+ // 如果模板路径以admin@开头
124
+ if (strpos($template, 'admin@') === 0) {
125
+ $parts = explode('@', $template);
126
+ $result = $parts[1];
127
+ $template = APP_PATH . request()->module() . '/view_new/' . $result . '.html';
128
+ }
129
+ }
130
+ return $this->view->fetch($template, $vars, $replace, $config);
131
+ }
132
+
133
+ /**
134
+ * 渲染内容输出
135
+ * @access protected
136
+ * @param string $content 模板内容
137
+ * @param array $vars 模板输出变量
138
+ * @param array $replace 替换内容
139
+ * @param array $config 模板参数
140
+ * @return mixed
141
+ */
142
+ protected function display($content = '', $vars = [], $replace = [], $config = [])
143
+ {
144
+ return $this->view->display($content, $vars, $replace, $config);
145
+ }
146
+
147
+ /**
148
+ * 模板变量赋值
149
+ * @access protected
150
+ * @param mixed $name 要显示的模板变量
151
+ * @param mixed $value 变量的值
152
+ * @return $this
153
+ */
154
+ protected function assign($name, $value = '')
155
+ {
156
+ $this->view->assign($name, $value);
157
+
158
+ return $this;
159
+ }
160
+
161
+ /**
162
+ * 初始化模板引擎
163
+ * @access protected
164
+ * @param array|string $engine 引擎参数
165
+ * @return $this
166
+ */
167
+ protected function engine($engine)
168
+ {
169
+ $this->view->engine($engine);
170
+
171
+ return $this;
172
+ }
173
+
174
+ /**
175
+ * 设置验证失败后是否抛出异常
176
+ * @access protected
177
+ * @param bool $fail 是否抛出异常
178
+ * @return $this
179
+ */
180
+ protected function validateFailException($fail = true)
181
+ {
182
+ $this->failException = $fail;
183
+
184
+ return $this;
185
+ }
186
+
187
+ /**
188
+ * 验证数据
189
+ * @access protected
190
+ * @param array $data 数据
191
+ * @param string|array $validate 验证器名或者验证规则数组
192
+ * @param array $message 提示信息
193
+ * @param bool $batch 是否批量验证
194
+ * @param mixed $callback 回调方法(闭包)
195
+ * @return array|string|true
196
+ * @throws ValidateException
197
+ */
198
+ protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
199
+ {
200
+ if (is_array($validate)) {
201
+ $v = Loader::validate();
202
+ $v->rule($validate);
203
+ } else {
204
+ // 支持场景
205
+ if (strpos($validate, '.')) {
206
+ list($validate, $scene) = explode('.', $validate);
207
+ }
208
+
209
+ $v = Loader::validate($validate);
210
+
211
+ !empty($scene) && $v->scene($scene);
212
+ }
213
+
214
+ // 批量验证
215
+ if ($batch || $this->batchValidate) {
216
+ $v->batch(true);
217
+ }
218
+
219
+ // 设置错误信息
220
+ if (is_array($message)) {
221
+ $v->message($message);
222
+ }
223
+
224
+ // 使用回调验证
225
+ if ($callback && is_callable($callback)) {
226
+ call_user_func_array($callback, [$v, &$data]);
227
+ }
228
+
229
+ if (!$v->check($data)) {
230
+ if ($this->failException) {
231
+ throw new ValidateException($v->getError());
232
+ }
233
+
234
+ return $v->getError();
235
+ }
236
+
237
+ return true;
238
+ }
239
+ }
thinkphp/library/think/Cookie.php ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Cookie
15
+ {
16
+ /**
17
+ * @var array cookie 设置参数
18
+ */
19
+ protected static $config = [
20
+ 'prefix' => '', // cookie 名称前缀
21
+ 'expire' => 0, // cookie 保存时间
22
+ 'path' => '/', // cookie 保存路径
23
+ 'domain' => '', // cookie 有效域名
24
+ 'secure' => false, // cookie 启用安全传输
25
+ 'httponly' => false, // httponly 设置
26
+ 'setcookie' => true, // 是否使用 setcookie
27
+ ];
28
+
29
+ /**
30
+ * @var bool 是否完成初始化了
31
+ */
32
+ protected static $init;
33
+
34
+ /**
35
+ * Cookie初始化
36
+ * @access public
37
+ * @param array $config 配置参数
38
+ * @return void
39
+ */
40
+ public static function init(array $config = [])
41
+ {
42
+ if (empty($config)) {
43
+ $config = Config::get('cookie');
44
+ }
45
+
46
+ self::$config = array_merge(self::$config, array_change_key_case($config));
47
+
48
+ if (!empty(self::$config['httponly'])) {
49
+ ini_set('session.cookie_httponly', 1);
50
+ }
51
+
52
+ self::$init = true;
53
+ }
54
+
55
+ /**
56
+ * 设置或者获取 cookie 作用域(前缀)
57
+ * @access public
58
+ * @param string $prefix 前缀
59
+ * @return string|
60
+ */
61
+ public static function prefix($prefix = '')
62
+ {
63
+ if (empty($prefix)) {
64
+ return self::$config['prefix'];
65
+ }
66
+
67
+ return self::$config['prefix'] = $prefix;
68
+ }
69
+
70
+ /**
71
+ * Cookie 设置、获取、删除
72
+ * @access public
73
+ * @param string $name cookie 名称
74
+ * @param mixed $value cookie 值
75
+ * @param mixed $option 可选参数 可能会是 null|integer|string
76
+ * @return void
77
+ */
78
+ public static function set($name, $value = '', $option = null)
79
+ {
80
+ !isset(self::$init) && self::init();
81
+
82
+ // 参数设置(会覆盖黙认设置)
83
+ if (!is_null($option)) {
84
+ if (is_numeric($option)) {
85
+ $option = ['expire' => $option];
86
+ } elseif (is_string($option)) {
87
+ parse_str($option, $option);
88
+ }
89
+
90
+ $config = array_merge(self::$config, array_change_key_case($option));
91
+ } else {
92
+ $config = self::$config;
93
+ }
94
+
95
+ $name = $config['prefix'] . $name;
96
+
97
+ // 设置 cookie
98
+ if (is_array($value)) {
99
+ array_walk_recursive($value, 'self::jsonFormatProtect', 'encode');
100
+ $value = 'think:' . json_encode($value);
101
+ }
102
+
103
+ $expire = !empty($config['expire']) ?
104
+ $_SERVER['REQUEST_TIME'] + intval($config['expire']) :
105
+ 0;
106
+
107
+ if ($config['setcookie']) {
108
+ setcookie(
109
+ $name, $value, $expire, $config['path'], $config['domain'],
110
+ $config['secure'], $config['httponly']
111
+ );
112
+ }
113
+
114
+ $_COOKIE[$name] = $value;
115
+ }
116
+
117
+ /**
118
+ * 永久保存 Cookie 数据
119
+ * @access public
120
+ * @param string $name cookie 名称
121
+ * @param mixed $value cookie 值
122
+ * @param mixed $option 可选参数 可能会是 null|integer|string
123
+ * @return void
124
+ */
125
+ public static function forever($name, $value = '', $option = null)
126
+ {
127
+ if (is_null($option) || is_numeric($option)) {
128
+ $option = [];
129
+ }
130
+
131
+ $option['expire'] = 315360000;
132
+
133
+ self::set($name, $value, $option);
134
+ }
135
+
136
+ /**
137
+ * 判断是否有 Cookie 数据
138
+ * @access public
139
+ * @param string $name cookie 名称
140
+ * @param string|null $prefix cookie 前缀
141
+ * @return bool
142
+ */
143
+ public static function has($name, $prefix = null)
144
+ {
145
+ !isset(self::$init) && self::init();
146
+
147
+ $prefix = !is_null($prefix) ? $prefix : self::$config['prefix'];
148
+
149
+ return isset($_COOKIE[$prefix . $name]);
150
+ }
151
+
152
+ /**
153
+ * 获取 Cookie 的值
154
+ * @access public
155
+ * @param string $name cookie 名称
156
+ * @param string|null $prefix cookie 前缀
157
+ * @return mixed
158
+ */
159
+ public static function get($name = '', $prefix = null)
160
+ {
161
+ !isset(self::$init) && self::init();
162
+
163
+ $prefix = !is_null($prefix) ? $prefix : self::$config['prefix'];
164
+ $key = $prefix . $name;
165
+
166
+ if ('' == $name) {
167
+ // 获取全部
168
+ if ($prefix) {
169
+ $value = [];
170
+
171
+ foreach ($_COOKIE as $k => $val) {
172
+ if (0 === strpos($k, $prefix)) {
173
+ $value[$k] = $val;
174
+ }
175
+
176
+ }
177
+ } else {
178
+ $value = $_COOKIE;
179
+ }
180
+ } elseif (isset($_COOKIE[$key])) {
181
+ $value = $_COOKIE[$key];
182
+
183
+ if (0 === strpos($value, 'think:')) {
184
+ $value = json_decode(substr($value, 6), true);
185
+ array_walk_recursive($value, 'self::jsonFormatProtect', 'decode');
186
+ }
187
+ } else {
188
+ $value = null;
189
+ }
190
+
191
+ return $value;
192
+ }
193
+
194
+ /**
195
+ * 删除 Cookie
196
+ * @access public
197
+ * @param string $name cookie 名称
198
+ * @param string|null $prefix cookie 前缀
199
+ * @return void
200
+ */
201
+ public static function delete($name, $prefix = null)
202
+ {
203
+ !isset(self::$init) && self::init();
204
+
205
+ $config = self::$config;
206
+ $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
207
+ $name = $prefix . $name;
208
+
209
+ if ($config['setcookie']) {
210
+ setcookie(
211
+ $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'],
212
+ $config['domain'], $config['secure'], $config['httponly']
213
+ );
214
+ }
215
+
216
+ // 删除指定 cookie
217
+ unset($_COOKIE[$name]);
218
+ }
219
+
220
+ /**
221
+ * 清除指定前缀的所有 cookie
222
+ * @access public
223
+ * @param string|null $prefix cookie 前缀
224
+ * @return void
225
+ */
226
+ public static function clear($prefix = null)
227
+ {
228
+ if (empty($_COOKIE)) {
229
+ return;
230
+ }
231
+
232
+ !isset(self::$init) && self::init();
233
+
234
+ // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀
235
+ $config = self::$config;
236
+ $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
237
+
238
+ if ($prefix) {
239
+ foreach ($_COOKIE as $key => $val) {
240
+ if (0 === strpos($key, $prefix)) {
241
+ if ($config['setcookie']) {
242
+ setcookie(
243
+ $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'],
244
+ $config['domain'], $config['secure'], $config['httponly']
245
+ );
246
+ }
247
+
248
+ unset($_COOKIE[$key]);
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * json 转换时的格式保护
256
+ * @access protected
257
+ * @param mixed $val 要转换的值
258
+ * @param string $key 键名
259
+ * @param string $type 转换类别
260
+ * @return void
261
+ */
262
+ protected static function jsonFormatProtect(&$val, $key, $type = 'encode')
263
+ {
264
+ if (!empty($val) && true !== $val) {
265
+ $val = 'decode' == $type ? urldecode($val) : urlencode($val);
266
+ }
267
+ }
268
+ }
thinkphp/library/think/Db.php ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\db\Connection;
15
+ use think\db\Query;
16
+
17
+ /**
18
+ * Class Db
19
+ * @package think
20
+ * @method Query table(string $table) static 指定数据表(含前缀)
21
+ * @method Query name(string $name) static 指定数据表(不含前缀)
22
+ * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
23
+ * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询
24
+ * @method Query union(mixed $union, boolean $all = false) static UNION查询
25
+ * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT
26
+ * @method Query order(mixed $field, string $order = null) static 查询ORDER
27
+ * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存
28
+ * @method mixed value(string $field) static 获取某个字段的值
29
+ * @method array column(string $field, string $key = '') static 获取某个列的值
30
+ * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询
31
+ * @method mixed find(mixed $data = null) static 查询单个记录
32
+ * @method mixed select(mixed $data = null) static 查询多个记录
33
+ * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录
34
+ * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID
35
+ * @method integer insertAll(array $dataSet) static 插入多条记录
36
+ * @method integer update(array $data) static 更新记录
37
+ * @method integer delete(mixed $data = null) static 删除记录
38
+ * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据
39
+ * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询
40
+ * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行
41
+ * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询
42
+ * @method mixed transaction(callable $callback) static 执行数据库事务
43
+ * @method void startTrans() static 启动事务
44
+ * @method void commit() static 用于非自动提交状态下面的查询提交
45
+ * @method void rollback() static 事务回滚
46
+ * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句
47
+ * @method string quote(string $str) static SQL指令安全过滤
48
+ * @method string getLastInsID($sequence = null) static 获取最近插入的ID
49
+ */
50
+ class Db
51
+ {
52
+ /**
53
+ * @var Connection[] 数据库连接实例
54
+ */
55
+ private static $instance = [];
56
+
57
+ /**
58
+ * @var int 查询次数
59
+ */
60
+ public static $queryTimes = 0;
61
+
62
+ /**
63
+ * @var int 执行次数
64
+ */
65
+ public static $executeTimes = 0;
66
+
67
+ /**
68
+ * 数据库初始化,并取得数据库类实例
69
+ * @access public
70
+ * @param mixed $config 连接配置
71
+ * @param bool|string $name 连接标识 true 强制重新连接
72
+ * @return Connection
73
+ * @throws Exception
74
+ */
75
+ public static function connect($config = [], $name = false)
76
+ {
77
+ if (false === $name) {
78
+ $name = md5(serialize($config));
79
+ }
80
+
81
+ if (true === $name || !isset(self::$instance[$name])) {
82
+ // 解析连接参数 支持数组和字符串
83
+ $options = self::parseConfig($config);
84
+
85
+ if (empty($options['type'])) {
86
+ throw new \InvalidArgumentException('Undefined db type');
87
+ }
88
+
89
+ $class = false !== strpos($options['type'], '\\') ?
90
+ $options['type'] :
91
+ '\\think\\db\\connector\\' . ucwords($options['type']);
92
+
93
+ // 记录初始化信息
94
+ if (App::$debug) {
95
+ Log::record('[ DB ] INIT ' . $options['type'], 'info');
96
+ }
97
+
98
+ if (true === $name) {
99
+ $name = md5(serialize($config));
100
+ }
101
+
102
+ self::$instance[$name] = new $class($options);
103
+ }
104
+
105
+ return self::$instance[$name];
106
+ }
107
+
108
+ /**
109
+ * 清除连接实例
110
+ * @access public
111
+ * @return void
112
+ */
113
+ public static function clear()
114
+ {
115
+ self::$instance = [];
116
+ }
117
+
118
+ /**
119
+ * 数据库连接参数解析
120
+ * @access private
121
+ * @param mixed $config 连接参数
122
+ * @return array
123
+ */
124
+ private static function parseConfig($config)
125
+ {
126
+ if (empty($config)) {
127
+ $config = Config::get('database');
128
+ } elseif (is_string($config) && false === strpos($config, '/')) {
129
+ $config = Config::get($config); // 支持读取配置参数
130
+ }
131
+
132
+ return is_string($config) ? self::parseDsn($config) : $config;
133
+ }
134
+
135
+ /**
136
+ * DSN 解析
137
+ * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1&param2=val2#utf8
138
+ * @access private
139
+ * @param string $dsnStr 数据库 DSN 字符串解析
140
+ * @return array
141
+ */
142
+ private static function parseDsn($dsnStr)
143
+ {
144
+ $info = parse_url($dsnStr);
145
+
146
+ if (!$info) {
147
+ return [];
148
+ }
149
+
150
+ $dsn = [
151
+ 'type' => $info['scheme'],
152
+ 'username' => isset($info['user']) ? $info['user'] : '',
153
+ 'password' => isset($info['pass']) ? $info['pass'] : '',
154
+ 'hostname' => isset($info['host']) ? $info['host'] : '',
155
+ 'hostport' => isset($info['port']) ? $info['port'] : '',
156
+ 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '',
157
+ 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8',
158
+ ];
159
+
160
+ if (isset($info['query'])) {
161
+ parse_str($info['query'], $dsn['params']);
162
+ } else {
163
+ $dsn['params'] = [];
164
+ }
165
+
166
+ return $dsn;
167
+ }
168
+
169
+ /**
170
+ * 调用驱动类的方法
171
+ * @access public
172
+ * @param string $method 方法名
173
+ * @param array $params 参数
174
+ * @return mixed
175
+ */
176
+ public static function __callStatic($method, $params)
177
+ {
178
+ return call_user_func_array([self::connect(), $method], $params);
179
+ }
180
+ }
thinkphp/library/think/Debug.php ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\ClassNotFoundException;
15
+ use think\response\Redirect;
16
+
17
+ class Debug
18
+ {
19
+ /**
20
+ * @var array 区间时间信息
21
+ */
22
+ protected static $info = [];
23
+
24
+ /**
25
+ * @var array 区间内存信息
26
+ */
27
+ protected static $mem = [];
28
+
29
+ /**
30
+ * 记录时间(微秒)和内存使用情况
31
+ * @access public
32
+ * @param string $name 标记位置
33
+ * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存)
34
+ * @return void
35
+ */
36
+ public static function remark($name, $value = '')
37
+ {
38
+ self::$info[$name] = is_float($value) ? $value : microtime(true);
39
+
40
+ if ('time' != $value) {
41
+ self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage();
42
+ self::$mem['peak'][$name] = memory_get_peak_usage();
43
+ }
44
+ }
45
+
46
+ /**
47
+ * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位
48
+ * @access public
49
+ * @param string $start 开始标签
50
+ * @param string $end 结束标签
51
+ * @param integer $dec 小数位
52
+ * @return string
53
+ */
54
+ public static function getRangeTime($start, $end, $dec = 6)
55
+ {
56
+ if (!isset(self::$info[$end])) {
57
+ self::$info[$end] = microtime(true);
58
+ }
59
+
60
+ return number_format((self::$info[$end] - self::$info[$start]), $dec);
61
+ }
62
+
63
+ /**
64
+ * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位
65
+ * @access public
66
+ * @param integer $dec 小数位
67
+ * @return string
68
+ */
69
+ public static function getUseTime($dec = 6)
70
+ {
71
+ return number_format((microtime(true) - THINK_START_TIME), $dec);
72
+ }
73
+
74
+ /**
75
+ * 获取当前访问的吞吐率情况
76
+ * @access public
77
+ * @return string
78
+ */
79
+ public static function getThroughputRate()
80
+ {
81
+ return number_format(1 / self::getUseTime(), 2) . 'req/s';
82
+ }
83
+
84
+ /**
85
+ * 记录区间的内存使用情况
86
+ * @access public
87
+ * @param string $start 开始标签
88
+ * @param string $end 结束标签
89
+ * @param integer $dec 小数位
90
+ * @return string
91
+ */
92
+ public static function getRangeMem($start, $end, $dec = 2)
93
+ {
94
+ if (!isset(self::$mem['mem'][$end])) {
95
+ self::$mem['mem'][$end] = memory_get_usage();
96
+ }
97
+
98
+ $size = self::$mem['mem'][$end] - self::$mem['mem'][$start];
99
+ $a = ['B', 'KB', 'MB', 'GB', 'TB'];
100
+ $pos = 0;
101
+
102
+ while ($size >= 1024) {
103
+ $size /= 1024;
104
+ $pos++;
105
+ }
106
+
107
+ return round($size, $dec) . " " . $a[$pos];
108
+ }
109
+
110
+ /**
111
+ * 统计从开始到统计时的内存使用情况
112
+ * @access public
113
+ * @param integer $dec 小数位
114
+ * @return string
115
+ */
116
+ public static function getUseMem($dec = 2)
117
+ {
118
+ $size = memory_get_usage() - THINK_START_MEM;
119
+ $a = ['B', 'KB', 'MB', 'GB', 'TB'];
120
+ $pos = 0;
121
+
122
+ while ($size >= 1024) {
123
+ $size /= 1024;
124
+ $pos++;
125
+ }
126
+
127
+ return round($size, $dec) . " " . $a[$pos];
128
+ }
129
+
130
+ /**
131
+ * 统计区间的内存峰值情况
132
+ * @access public
133
+ * @param string $start 开始标签
134
+ * @param string $end 结束标签
135
+ * @param integer $dec 小数位
136
+ * @return string
137
+ */
138
+ public static function getMemPeak($start, $end, $dec = 2)
139
+ {
140
+ if (!isset(self::$mem['peak'][$end])) {
141
+ self::$mem['peak'][$end] = memory_get_peak_usage();
142
+ }
143
+
144
+ $size = self::$mem['peak'][$end] - self::$mem['peak'][$start];
145
+ $a = ['B', 'KB', 'MB', 'GB', 'TB'];
146
+ $pos = 0;
147
+
148
+ while ($size >= 1024) {
149
+ $size /= 1024;
150
+ $pos++;
151
+ }
152
+
153
+ return round($size, $dec) . " " . $a[$pos];
154
+ }
155
+
156
+ /**
157
+ * 获取文件加载信息
158
+ * @access public
159
+ * @param bool $detail 是否显示详细
160
+ * @return integer|array
161
+ */
162
+ public static function getFile($detail = false)
163
+ {
164
+ $files = get_included_files();
165
+
166
+ if ($detail) {
167
+ $info = [];
168
+
169
+ foreach ($files as $file) {
170
+ $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
171
+ }
172
+
173
+ return $info;
174
+ }
175
+
176
+ return count($files);
177
+ }
178
+
179
+ /**
180
+ * 浏览器友好的变量输出
181
+ * @access public
182
+ * @param mixed $var 变量
183
+ * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串)
184
+ * @param string|null $label 标签(默认为空)
185
+ * @param integer $flags htmlspecialchars 的标志
186
+ * @return null|string
187
+ */
188
+ public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE)
189
+ {
190
+ $label = (null === $label) ? '' : rtrim($label) . ':';
191
+
192
+ ob_start();
193
+ var_dump($var);
194
+ $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean());
195
+
196
+ if (IS_CLI) {
197
+ $output = PHP_EOL . $label . $output . PHP_EOL;
198
+ } else {
199
+ if (!extension_loaded('xdebug')) {
200
+ $output = htmlspecialchars($output, $flags);
201
+ }
202
+
203
+ $output = '<pre>' . $label . $output . '</pre>';
204
+ }
205
+
206
+ if ($echo) {
207
+ echo($output);
208
+ return;
209
+ }
210
+
211
+ return $output;
212
+ }
213
+
214
+ /**
215
+ * 调试信息注入到响应中
216
+ * @access public
217
+ * @param Response $response 响应实例
218
+ * @param string $content 返回的字符串
219
+ * @return void
220
+ */
221
+ public static function inject(Response $response, &$content)
222
+ {
223
+ $config = Config::get('trace');
224
+ $type = isset($config['type']) ? $config['type'] : 'Html';
225
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type);
226
+
227
+ unset($config['type']);
228
+
229
+ if (!class_exists($class)) {
230
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
231
+ }
232
+
233
+ /** @var \think\debug\Console|\think\debug\Html $trace */
234
+ $trace = new $class($config);
235
+
236
+ if ($response instanceof Redirect) {
237
+ // TODO 记录
238
+ } else {
239
+ $output = $trace->output($response, Log::getLog());
240
+
241
+ if (is_string($output)) {
242
+ // trace 调试信息注入
243
+ $pos = strripos($content, '</body>');
244
+ if (false !== $pos) {
245
+ $content = substr($content, 0, $pos) . $output . substr($content, $pos);
246
+ } else {
247
+ $content = $content . $output;
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
thinkphp/library/think/Env.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Env
15
+ {
16
+ /**
17
+ * 获取环境变量值
18
+ * @access public
19
+ * @param string $name 环境变量名(支持二级 . 号分割)
20
+ * @param string $default 默认值
21
+ * @return mixed
22
+ */
23
+ public static function get($name, $default = null)
24
+ {
25
+ $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name)));
26
+
27
+ if (false !== $result) {
28
+ if ('false' === $result) {
29
+ $result = false;
30
+ } elseif ('true' === $result) {
31
+ $result = true;
32
+ }
33
+
34
+ return $result;
35
+ }
36
+
37
+ return $default;
38
+ }
39
+ }
thinkphp/library/think/Error.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
10
+ // +----------------------------------------------------------------------
11
+
12
+ namespace think;
13
+
14
+ use think\console\Output as ConsoleOutput;
15
+ use think\exception\ErrorException;
16
+ use think\exception\Handle;
17
+ use think\exception\ThrowableError;
18
+
19
+ class Error
20
+ {
21
+ /**
22
+ * 注册异常处理
23
+ * @access public
24
+ * @return void
25
+ */
26
+ public static function register()
27
+ {
28
+ error_reporting(E_ALL);
29
+ set_error_handler([__CLASS__, 'appError']);
30
+ set_exception_handler([__CLASS__, 'appException']);
31
+ register_shutdown_function([__CLASS__, 'appShutdown']);
32
+ }
33
+
34
+ /**
35
+ * 异常处理
36
+ * @access public
37
+ * @param \Exception|\Throwable $e 异常
38
+ * @return void
39
+ */
40
+ public static function appException($e)
41
+ {
42
+ if (!$e instanceof \Exception) {
43
+ $e = new ThrowableError($e);
44
+ }
45
+
46
+ $handler = self::getExceptionHandler();
47
+ $handler->report($e);
48
+
49
+ if (IS_CLI) {
50
+ $handler->renderForConsole(new ConsoleOutput, $e);
51
+ } else {
52
+ $handler->render($e)->send();
53
+ }
54
+ }
55
+
56
+ /**
57
+ * 错误处理
58
+ * @access public
59
+ * @param integer $errno 错误编号
60
+ * @param integer $errstr 详细错误信息
61
+ * @param string $errfile 出错的文件
62
+ * @param integer $errline 出错行号
63
+ * @return void
64
+ * @throws ErrorException
65
+ */
66
+ public static function appError($errno, $errstr, $errfile = '', $errline = 0)
67
+ {
68
+ $exception = new ErrorException($errno, $errstr, $errfile, $errline);
69
+
70
+ // 符合异常处理的则将错误信息托管至 think\exception\ErrorException
71
+ if (error_reporting() & $errno) {
72
+ throw $exception;
73
+ }
74
+
75
+ self::getExceptionHandler()->report($exception);
76
+ }
77
+
78
+ /**
79
+ * 异常中止处理
80
+ * @access public
81
+ * @return void
82
+ */
83
+ public static function appShutdown()
84
+ {
85
+ // 将错误信息托管至 think\ErrorException
86
+ if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
87
+ self::appException(new ErrorException(
88
+ $error['type'], $error['message'], $error['file'], $error['line']
89
+ ));
90
+ }
91
+
92
+ // 写入日志
93
+ Log::save();
94
+ }
95
+
96
+ /**
97
+ * 确定错误类型是否致命
98
+ * @access protected
99
+ * @param int $type 错误类型
100
+ * @return bool
101
+ */
102
+ protected static function isFatal($type)
103
+ {
104
+ return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
105
+ }
106
+
107
+ /**
108
+ * 获取异常处理的实例
109
+ * @access public
110
+ * @return Handle
111
+ */
112
+ public static function getExceptionHandler()
113
+ {
114
+ static $handle;
115
+
116
+ if (!$handle) {
117
+ // 异常处理 handle
118
+ $class = Config::get('exception_handle');
119
+
120
+ if ($class && is_string($class) && class_exists($class) &&
121
+ is_subclass_of($class, "\\think\\exception\\Handle")
122
+ ) {
123
+ $handle = new $class;
124
+ } else {
125
+ $handle = new Handle;
126
+
127
+ if ($class instanceof \Closure) {
128
+ $handle->setRender($class);
129
+ }
130
+
131
+ }
132
+ }
133
+
134
+ return $handle;
135
+ }
136
+ }
thinkphp/library/think/Exception.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
10
+ // +----------------------------------------------------------------------
11
+
12
+ namespace think;
13
+
14
+ class Exception extends \Exception
15
+ {
16
+ /**
17
+ * @var array 保存异常页面显示的额外 Debug 数据
18
+ */
19
+ protected $data = [];
20
+
21
+ /**
22
+ * 设置异常额外的 Debug 数据
23
+ * 数据将会显示为下面的格式
24
+ *
25
+ * Exception Data
26
+ * --------------------------------------------------
27
+ * Label 1
28
+ * key1 value1
29
+ * key2 value2
30
+ * Label 2
31
+ * key1 value1
32
+ * key2 value2
33
+ *
34
+ * @access protected
35
+ * @param string $label 数据分类,用于异常页面显示
36
+ * @param array $data 需要显示的数据,必须为关联数组
37
+ * @return void
38
+ */
39
+ final protected function setData($label, array $data)
40
+ {
41
+ $this->data[$label] = $data;
42
+ }
43
+
44
+ /**
45
+ * 获取异常额外 Debug 数据
46
+ * 主要用于输出到异常页面便于调试
47
+ * @access public
48
+ * @return array
49
+ */
50
+ final public function getData()
51
+ {
52
+ return $this->data;
53
+ }
54
+
55
+ }
thinkphp/library/think/File.php ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use SplFileObject;
15
+
16
+ class File extends SplFileObject
17
+ {
18
+ /**
19
+ * @var string 错误信息
20
+ */
21
+ private $error = '';
22
+
23
+ /**
24
+ * @var string 当前完整文件名
25
+ */
26
+ protected $filename;
27
+
28
+ /**
29
+ * @var string 上传文件名
30
+ */
31
+ protected $saveName;
32
+
33
+ /**
34
+ * @var string 文件上传命名规则
35
+ */
36
+ protected $rule = 'date';
37
+
38
+ /**
39
+ * @var array 文件上传验证规则
40
+ */
41
+ protected $validate = [];
42
+
43
+ /**
44
+ * @var bool 单元测试
45
+ */
46
+ protected $isTest;
47
+
48
+ /**
49
+ * @var array 上传文件信息
50
+ */
51
+ protected $info;
52
+
53
+ /**
54
+ * @var array 文件 hash 信息
55
+ */
56
+ protected $hash = [];
57
+
58
+ /**
59
+ * File constructor.
60
+ * @access public
61
+ * @param string $filename 文件名称
62
+ * @param string $mode 访问模式
63
+ */
64
+ public function __construct($filename, $mode = 'r')
65
+ {
66
+ parent::__construct($filename, $mode);
67
+ $this->filename = $this->getRealPath() ?: $this->getPathname();
68
+ }
69
+
70
+ /**
71
+ * 设置是否是单元测试
72
+ * @access public
73
+ * @param bool $test 是否是测试
74
+ * @return $this
75
+ */
76
+ public function isTest($test = false)
77
+ {
78
+ $this->isTest = $test;
79
+
80
+ return $this;
81
+ }
82
+
83
+ /**
84
+ * 设置上传信息
85
+ * @access public
86
+ * @param array $info 上传文件信息
87
+ * @return $this
88
+ */
89
+ public function setUploadInfo($info)
90
+ {
91
+ $this->info = $info;
92
+
93
+ return $this;
94
+ }
95
+
96
+ /**
97
+ * 获取上传文件的信息
98
+ * @access public
99
+ * @param string $name 信息名称
100
+ * @return array|string
101
+ */
102
+ public function getInfo($name = '')
103
+ {
104
+ return isset($this->info[$name]) ? $this->info[$name] : $this->info;
105
+ }
106
+
107
+ /**
108
+ * 获取上传文件的文件名
109
+ * @access public
110
+ * @return string
111
+ */
112
+ public function getSaveName()
113
+ {
114
+ return $this->saveName;
115
+ }
116
+
117
+ /**
118
+ * 设置上传文件的保存文件名
119
+ * @access public
120
+ * @param string $saveName 保存名称
121
+ * @return $this
122
+ */
123
+ public function setSaveName($saveName)
124
+ {
125
+ $this->saveName = $saveName;
126
+
127
+ return $this;
128
+ }
129
+
130
+ /**
131
+ * 获取文件的哈希散列值
132
+ * @access public
133
+ * @param string $type 类型
134
+ * @return string
135
+ */
136
+ public function hash($type = 'sha1')
137
+ {
138
+ if (!isset($this->hash[$type])) {
139
+ $this->hash[$type] = hash_file($type, $this->filename);
140
+ }
141
+
142
+ return $this->hash[$type];
143
+ }
144
+
145
+ /**
146
+ * 检查目录是否可写
147
+ * @access protected
148
+ * @param string $path 目录
149
+ * @return boolean
150
+ */
151
+ protected function checkPath($path)
152
+ {
153
+ if (is_dir($path) || mkdir($path, 0755, true)) {
154
+ return true;
155
+ }
156
+
157
+ $this->error = ['directory {:path} creation failed', ['path' => $path]];
158
+
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * 获取文件类型信息
164
+ * @access public
165
+ * @return string
166
+ */
167
+ public function getMime()
168
+ {
169
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
170
+
171
+ return finfo_file($finfo, $this->filename);
172
+ }
173
+
174
+ /**
175
+ * 设置文件的命名规则
176
+ * @access public
177
+ * @param string $rule 文件命名规则
178
+ * @return $this
179
+ */
180
+ public function rule($rule)
181
+ {
182
+ $this->rule = $rule;
183
+
184
+ return $this;
185
+ }
186
+
187
+ /**
188
+ * 设置上传文件的验证规则
189
+ * @access public
190
+ * @param array $rule 验证规则
191
+ * @return $this
192
+ */
193
+ public function validate(array $rule = [])
194
+ {
195
+ $this->validate = $rule;
196
+
197
+ return $this;
198
+ }
199
+
200
+ /**
201
+ * 检测是否合法的上传文件
202
+ * @access public
203
+ * @return bool
204
+ */
205
+ public function isValid()
206
+ {
207
+ return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename);
208
+ }
209
+
210
+ /**
211
+ * 检测上传文件
212
+ * @access public
213
+ * @param array $rule 验证规则
214
+ * @return bool
215
+ */
216
+ public function check($rule = [])
217
+ {
218
+ $rule = $rule ?: $this->validate;
219
+
220
+ /* 检查文件大小 */
221
+ if (isset($rule['size']) && !$this->checkSize($rule['size'])) {
222
+ $this->error = 'filesize not match';
223
+ return false;
224
+ }
225
+
226
+ /* 检查文件 Mime 类型 */
227
+ if (isset($rule['type']) && !$this->checkMime($rule['type'])) {
228
+ $this->error = 'mimetype to upload is not allowed';
229
+ return false;
230
+ }
231
+
232
+ /* 检查文件后缀 */
233
+ if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) {
234
+ $this->error = 'extensions to upload is not allowed';
235
+ return false;
236
+ }
237
+
238
+ /* 检查图像文件 */
239
+ if (!$this->checkImg()) {
240
+ $this->error = 'illegal image files';
241
+ return false;
242
+ }
243
+
244
+ return true;
245
+ }
246
+
247
+ /**
248
+ * 检测上传文件后缀
249
+ * @access public
250
+ * @param array|string $ext 允许后缀
251
+ * @return bool
252
+ */
253
+ public function checkExt($ext)
254
+ {
255
+ if (is_string($ext)) {
256
+ $ext = explode(',', $ext);
257
+ }
258
+
259
+ $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
260
+
261
+ return in_array($extension, $ext);
262
+ }
263
+
264
+ /**
265
+ * 检测图像文件
266
+ * @access public
267
+ * @return bool
268
+ */
269
+ public function checkImg()
270
+ {
271
+ $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
272
+
273
+ // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true
274
+ return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]);
275
+ }
276
+
277
+ /**
278
+ * 判断图像类型
279
+ * @access protected
280
+ * @param string $image 图片名称
281
+ * @return bool|int
282
+ */
283
+ protected function getImageType($image)
284
+ {
285
+ if (function_exists('exif_imagetype')) {
286
+ return exif_imagetype($image);
287
+ }
288
+
289
+ try {
290
+ $info = getimagesize($image);
291
+ return $info ? $info[2] : false;
292
+ } catch (\Exception $e) {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ /**
298
+ * 检测上传文件大小
299
+ * @access public
300
+ * @param integer $size 最大大小
301
+ * @return bool
302
+ */
303
+ public function checkSize($size)
304
+ {
305
+ return $this->getSize() <= $size;
306
+ }
307
+
308
+ /**
309
+ * 检测上传文件类型
310
+ * @access public
311
+ * @param array|string $mime 允许类型
312
+ * @return bool
313
+ */
314
+ public function checkMime($mime)
315
+ {
316
+ $mime = is_string($mime) ? explode(',', $mime) : $mime;
317
+
318
+ return in_array(strtolower($this->getMime()), $mime);
319
+ }
320
+
321
+ /**
322
+ * 移动文件
323
+ * @access public
324
+ * @param string $path 保存路径
325
+ * @param string|bool $savename 保存的文件名 默认自动生成
326
+ * @param boolean $replace 同名文件是否覆盖
327
+ * @return false|File
328
+ */
329
+ public function move($path, $savename = true, $replace = true)
330
+ {
331
+ // 文件上传失败,捕获错误代码
332
+ if (!empty($this->info['error'])) {
333
+ $this->error($this->info['error']);
334
+ return false;
335
+ }
336
+
337
+ // 检测合法性
338
+ if (!$this->isValid()) {
339
+ $this->error = 'upload illegal files';
340
+ return false;
341
+ }
342
+
343
+ // 验证上传
344
+ if (!$this->check()) {
345
+ return false;
346
+ }
347
+
348
+ $path = rtrim($path, DS) . DS;
349
+ // 文件保存命名规则
350
+ $saveName = $this->buildSaveName($savename);
351
+ $filename = $path . $saveName;
352
+
353
+ // 检测目录
354
+ if (false === $this->checkPath(dirname($filename))) {
355
+ return false;
356
+ }
357
+
358
+ // 不覆盖同名文件
359
+ if (!$replace && is_file($filename)) {
360
+ $this->error = ['has the same filename: {:filename}', ['filename' => $filename]];
361
+ return false;
362
+ }
363
+
364
+ /* 移动文件 */
365
+ if ($this->isTest) {
366
+ rename($this->filename, $filename);
367
+ } elseif (!move_uploaded_file($this->filename, $filename)) {
368
+ $this->error = 'upload write error';
369
+ return false;
370
+ }
371
+
372
+ // 返回 File 对象实例
373
+ $file = new self($filename);
374
+ $file->setSaveName($saveName)->setUploadInfo($this->info);
375
+
376
+ return $file;
377
+ }
378
+
379
+ /**
380
+ * 获取保存文件名
381
+ * @access protected
382
+ * @param string|bool $savename 保存的文件名 默认自动生成
383
+ * @return string
384
+ */
385
+ protected function buildSaveName($savename)
386
+ {
387
+ // 自动生成文件名
388
+ if (true === $savename) {
389
+ if ($this->rule instanceof \Closure) {
390
+ $savename = call_user_func_array($this->rule, [$this]);
391
+ } else {
392
+ switch ($this->rule) {
393
+ case 'date':
394
+ $savename = date('Ymd') . DS . md5(microtime(true));
395
+ break;
396
+ default:
397
+ if (in_array($this->rule, hash_algos())) {
398
+ $hash = $this->hash($this->rule);
399
+ $savename = substr($hash, 0, 2) . DS . substr($hash, 2);
400
+ } elseif (is_callable($this->rule)) {
401
+ $savename = call_user_func($this->rule);
402
+ } else {
403
+ $savename = date('Ymd') . DS . md5(microtime(true));
404
+ }
405
+ }
406
+ }
407
+ } elseif ('' === $savename || false === $savename) {
408
+ $savename = $this->getInfo('name');
409
+ }
410
+
411
+ if (!strpos($savename, '.')) {
412
+ $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION);
413
+ }
414
+
415
+ return $savename;
416
+ }
417
+
418
+ /**
419
+ * 获取错误代码信息
420
+ * @access private
421
+ * @param int $errorNo 错误号
422
+ * @return $this
423
+ */
424
+ private function error($errorNo)
425
+ {
426
+ switch ($errorNo) {
427
+ case 1:
428
+ case 2:
429
+ $this->error = 'upload File size exceeds the maximum value';
430
+ break;
431
+ case 3:
432
+ $this->error = 'only the portion of file is uploaded';
433
+ break;
434
+ case 4:
435
+ $this->error = 'no file to uploaded';
436
+ break;
437
+ case 6:
438
+ $this->error = 'upload temp dir not found';
439
+ break;
440
+ case 7:
441
+ $this->error = 'file write error';
442
+ break;
443
+ default:
444
+ $this->error = 'unknown upload error';
445
+ }
446
+
447
+ return $this;
448
+ }
449
+
450
+ /**
451
+ * 获取错误信息(支持多语言)
452
+ * @access public
453
+ * @return string
454
+ */
455
+ public function getError()
456
+ {
457
+ if (is_array($this->error)) {
458
+ list($msg, $vars) = $this->error;
459
+ } else {
460
+ $msg = $this->error;
461
+ $vars = [];
462
+ }
463
+
464
+ return Lang::has($msg) ? Lang::get($msg, $vars) : $msg;
465
+ }
466
+
467
+ /**
468
+ * 魔法方法,获取文件的 hash 值
469
+ * @access public
470
+ * @param string $method 方法名
471
+ * @param mixed $args 调用参数
472
+ * @return string
473
+ */
474
+ public function __call($method, $args)
475
+ {
476
+ return $this->hash($method);
477
+ }
478
+ }
thinkphp/library/think/Hook.php ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Hook
15
+ {
16
+ /**
17
+ * @var array 标签
18
+ */
19
+ private static $tags = [];
20
+
21
+ /**
22
+ * 动态添加行为扩展到某个标签
23
+ * @access public
24
+ * @param string $tag 标签名称
25
+ * @param mixed $behavior 行为名称
26
+ * @param bool $first 是否放到开头执行
27
+ * @return void
28
+ */
29
+ public static function add($tag, $behavior, $first = false)
30
+ {
31
+ isset(self::$tags[$tag]) || self::$tags[$tag] = [];
32
+
33
+ if (is_array($behavior) && !is_callable($behavior)) {
34
+ if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) {
35
+ unset($behavior['_overlay']);
36
+ self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior);
37
+ } else {
38
+ unset($behavior['_overlay']);
39
+ self::$tags[$tag] = $behavior;
40
+ }
41
+ } elseif ($first) {
42
+ array_unshift(self::$tags[$tag], $behavior);
43
+ } else {
44
+ self::$tags[$tag][] = $behavior;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 批量导入插件
50
+ * @access public
51
+ * @param array $tags 插件信息
52
+ * @param boolean $recursive 是否递归合并
53
+ * @return void
54
+ */
55
+ public static function import(array $tags, $recursive = true)
56
+ {
57
+ if ($recursive) {
58
+ foreach ($tags as $tag => $behavior) {
59
+ self::add($tag, $behavior);
60
+ }
61
+ } else {
62
+ self::$tags = $tags + self::$tags;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 获取插件信息
68
+ * @access public
69
+ * @param string $tag 插件位置(留空获取全部)
70
+ * @return array
71
+ */
72
+ public static function get($tag = '')
73
+ {
74
+ if (empty($tag)) {
75
+ return self::$tags;
76
+ }
77
+
78
+ return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : [];
79
+ }
80
+
81
+ /**
82
+ * 监听标签的行为
83
+ * @access public
84
+ * @param string $tag 标签名称
85
+ * @param mixed $params 传入参数
86
+ * @param mixed $extra 额外参数
87
+ * @param bool $once 只获取一个有效返回值
88
+ * @return mixed
89
+ */
90
+ public static function listen($tag, &$params = null, $extra = null, $once = false)
91
+ {
92
+ $results = [];
93
+
94
+ foreach (static::get($tag) as $key => $name) {
95
+ $results[$key] = self::exec($name, $tag, $params, $extra);
96
+
97
+ // 如果返回 false,或者仅获取一个有效返回则中断行为执行
98
+ if (false === $results[$key] || (!is_null($results[$key]) && $once)) {
99
+ break;
100
+ }
101
+ }
102
+
103
+ return $once ? end($results) : $results;
104
+ }
105
+
106
+ /**
107
+ * 执行某个行为
108
+ * @access public
109
+ * @param mixed $class 要执行的行为
110
+ * @param string $tag 方法名(标签名)
111
+ * @param mixed $params 传人的参数
112
+ * @param mixed $extra 额外参数
113
+ * @return mixed
114
+ */
115
+ public static function exec($class, $tag = '', &$params = null, $extra = null)
116
+ {
117
+ App::$debug && Debug::remark('behavior_start', 'time');
118
+
119
+ $method = Loader::parseName($tag, 1, false);
120
+
121
+ if ($class instanceof \Closure) {
122
+ $result = call_user_func_array($class, [ & $params, $extra]);
123
+ $class = 'Closure';
124
+ } elseif (is_array($class)) {
125
+ list($class, $method) = $class;
126
+
127
+ $result = (new $class())->$method($params, $extra);
128
+ $class = $class . '->' . $method;
129
+ } elseif (is_object($class)) {
130
+ $result = $class->$method($params, $extra);
131
+ $class = get_class($class);
132
+ } elseif (strpos($class, '::')) {
133
+ $result = call_user_func_array($class, [ & $params, $extra]);
134
+ } else {
135
+ $obj = new $class();
136
+ $method = ($tag && is_callable([$obj, $method])) ? $method : 'run';
137
+ $result = $obj->$method($params, $extra);
138
+ }
139
+
140
+ if (App::$debug) {
141
+ Debug::remark('behavior_end', 'time');
142
+ Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info');
143
+ }
144
+
145
+ return $result;
146
+ }
147
+
148
+ }
thinkphp/library/think/Lang.php ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Lang
15
+ {
16
+ /**
17
+ * @var array 语言数据
18
+ */
19
+ private static $lang = [];
20
+
21
+ /**
22
+ * @var string 语言作用域
23
+ */
24
+ private static $range = 'zh-cn';
25
+
26
+ /**
27
+ * @var string 语言自动侦测的变量
28
+ */
29
+ protected static $langDetectVar = 'lang';
30
+
31
+ /**
32
+ * @var string 语言 Cookie 变量
33
+ */
34
+ protected static $langCookieVar = 'think_var';
35
+
36
+ /**
37
+ * @var int 语言 Cookie 的过期时间
38
+ */
39
+ protected static $langCookieExpire = 3600;
40
+
41
+ /**
42
+ * @var array 允许语言列表
43
+ */
44
+ protected static $allowLangList = [];
45
+
46
+ /**
47
+ * @var array Accept-Language 转义为对应语言包名称 系统默认配置
48
+ */
49
+ protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn'];
50
+
51
+ /**
52
+ * 设定当前的语言
53
+ * @access public
54
+ * @param string $range 语言作用域
55
+ * @return string
56
+ */
57
+ public static function range($range = '')
58
+ {
59
+ if ($range) {
60
+ self::$range = $range;
61
+ }
62
+
63
+ return self::$range;
64
+ }
65
+
66
+ /**
67
+ * 设置语言定义(不区分大小写)
68
+ * @access public
69
+ * @param string|array $name 语言变量
70
+ * @param string $value 语言值
71
+ * @param string $range 语言作用域
72
+ * @return mixed
73
+ */
74
+ public static function set($name, $value = null, $range = '')
75
+ {
76
+ $range = $range ?: self::$range;
77
+
78
+ if (!isset(self::$lang[$range])) {
79
+ self::$lang[$range] = [];
80
+ }
81
+
82
+ if (is_array($name)) {
83
+ return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range];
84
+ }
85
+
86
+ return self::$lang[$range][strtolower($name)] = $value;
87
+ }
88
+
89
+ /**
90
+ * 加载语言定义(不区分大小写)
91
+ * @access public
92
+ * @param array|string $file 语言文件
93
+ * @param string $range 语言作用域
94
+ * @return mixed
95
+ */
96
+ public static function load($file, $range = '')
97
+ {
98
+ $range = $range ?: self::$range;
99
+ $file = is_string($file) ? [$file] : $file;
100
+
101
+ if (!isset(self::$lang[$range])) {
102
+ self::$lang[$range] = [];
103
+ }
104
+
105
+ $lang = [];
106
+
107
+ foreach ($file as $_file) {
108
+ if (is_file($_file)) {
109
+ // 记录加载信息
110
+ App::$debug && Log::record('[ LANG ] ' . $_file, 'info');
111
+
112
+ $_lang = include $_file;
113
+
114
+ if (is_array($_lang)) {
115
+ $lang = array_change_key_case($_lang) + $lang;
116
+ }
117
+ }
118
+ }
119
+
120
+ if (!empty($lang)) {
121
+ self::$lang[$range] = $lang + self::$lang[$range];
122
+ }
123
+
124
+ return self::$lang[$range];
125
+ }
126
+
127
+ /**
128
+ * 获取语言定义(不区分大小写)
129
+ * @access public
130
+ * @param string|null $name 语言变量
131
+ * @param string $range 语言作用域
132
+ * @return mixed
133
+ */
134
+ public static function has($name, $range = '')
135
+ {
136
+ $range = $range ?: self::$range;
137
+
138
+ return isset(self::$lang[$range][strtolower($name)]);
139
+ }
140
+
141
+ /**
142
+ * 获取语言定义(不区分大小写)
143
+ * @access public
144
+ * @param string|null $name 语言变量
145
+ * @param array $vars 变量替换
146
+ * @param string $range 语言作用域
147
+ * @return mixed
148
+ */
149
+ public static function get($name = null, $vars = [], $range = '')
150
+ {
151
+ $range = $range ?: self::$range;
152
+
153
+ // 空参数返回所有定义
154
+ if (empty($name)) {
155
+ return self::$lang[$range];
156
+ }
157
+
158
+ $key = strtolower($name);
159
+ $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $name;
160
+
161
+ // 变量解析
162
+ if (!empty($vars) && is_array($vars)) {
163
+ /**
164
+ * Notes:
165
+ * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0
166
+ * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
167
+ */
168
+ if (key($vars) === 0) {
169
+ // 数字索引解析
170
+ array_unshift($vars, $value);
171
+ $value = call_user_func_array('sprintf', $vars);
172
+ } else {
173
+ // 关联索引解析
174
+ $replace = array_keys($vars);
175
+ foreach ($replace as &$v) {
176
+ $v = "{:{$v}}";
177
+ }
178
+ $value = str_replace($replace, $vars, $value);
179
+ }
180
+
181
+ }
182
+
183
+ return $value;
184
+ }
185
+
186
+ /**
187
+ * 自动侦测设置获取语言选择
188
+ * @access public
189
+ * @return string
190
+ */
191
+ public static function detect()
192
+ {
193
+ $langSet = '';
194
+
195
+ if (isset($_GET[self::$langDetectVar])) {
196
+ // url 中设置了语言变量
197
+ $langSet = strtolower($_GET[self::$langDetectVar]);
198
+ } elseif (isset($_COOKIE[self::$langCookieVar])) {
199
+ // Cookie 中设置了语言变量
200
+ $langSet = strtolower($_COOKIE[self::$langCookieVar]);
201
+ } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
202
+ // 自动侦测浏览器语言
203
+ preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
204
+ $langSet = strtolower($matches[1]);
205
+ $acceptLangs = Config::get('header_accept_lang');
206
+
207
+ if (isset($acceptLangs[$langSet])) {
208
+ $langSet = $acceptLangs[$langSet];
209
+ } elseif (isset(self::$acceptLanguage[$langSet])) {
210
+ $langSet = self::$acceptLanguage[$langSet];
211
+ }
212
+ }
213
+
214
+ // 合法的语言
215
+ if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) {
216
+ self::$range = $langSet ?: self::$range;
217
+ }
218
+
219
+ return self::$range;
220
+ }
221
+
222
+ /**
223
+ * 设置语言自动侦测的变量
224
+ * @access public
225
+ * @param string $var 变量名称
226
+ * @return void
227
+ */
228
+ public static function setLangDetectVar($var)
229
+ {
230
+ self::$langDetectVar = $var;
231
+ }
232
+
233
+ /**
234
+ * 设置语言的 cookie 保存变量
235
+ * @access public
236
+ * @param string $var 变量名称
237
+ * @return void
238
+ */
239
+ public static function setLangCookieVar($var)
240
+ {
241
+ self::$langCookieVar = $var;
242
+ }
243
+
244
+ /**
245
+ * 设置语言的 cookie 的过期时间
246
+ * @access public
247
+ * @param string $expire 过期时间
248
+ * @return void
249
+ */
250
+ public static function setLangCookieExpire($expire)
251
+ {
252
+ self::$langCookieExpire = $expire;
253
+ }
254
+
255
+ /**
256
+ * 设置允许的语言列表
257
+ * @access public
258
+ * @param array $list 语言列表
259
+ * @return void
260
+ */
261
+ public static function setAllowLangList($list)
262
+ {
263
+ self::$allowLangList = $list;
264
+ }
265
+ }
thinkphp/library/think/Loader.php ADDED
@@ -0,0 +1,677 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\ClassNotFoundException;
15
+
16
+ class Loader
17
+ {
18
+ /**
19
+ * @var array 实例数组
20
+ */
21
+ protected static $instance = [];
22
+
23
+ /**
24
+ * @var array 类名映射
25
+ */
26
+ protected static $classMap = [];
27
+
28
+ /**
29
+ * @var array 命名空间别名
30
+ */
31
+ protected static $namespaceAlias = [];
32
+
33
+ /**
34
+ * @var array PSR-4 命名空间前缀长度映射
35
+ */
36
+ private static $prefixLengthsPsr4 = [];
37
+
38
+ /**
39
+ * @var array PSR-4 的加载目录
40
+ */
41
+ private static $prefixDirsPsr4 = [];
42
+
43
+ /**
44
+ * @var array PSR-4 加载失败的回退目录
45
+ */
46
+ private static $fallbackDirsPsr4 = [];
47
+
48
+ /**
49
+ * @var array PSR-0 命名空间前缀映射
50
+ */
51
+ private static $prefixesPsr0 = [];
52
+
53
+ /**
54
+ * @var array PSR-0 加载失败的回退目录
55
+ */
56
+ private static $fallbackDirsPsr0 = [];
57
+
58
+ /**
59
+ * @var array 需要加载的文件
60
+ */
61
+ private static $files = [];
62
+
63
+ /**
64
+ * 自动加载
65
+ * @access public
66
+ * @param string $class 类名
67
+ * @return bool
68
+ */
69
+ public static function autoload($class)
70
+ {
71
+ // 检测命名空间别名
72
+ if (!empty(self::$namespaceAlias)) {
73
+ $namespace = dirname($class);
74
+ if (isset(self::$namespaceAlias[$namespace])) {
75
+ $original = self::$namespaceAlias[$namespace] . '\\' . basename($class);
76
+ if (class_exists($original)) {
77
+ return class_alias($original, $class, false);
78
+ }
79
+ }
80
+ }
81
+
82
+ if ($file = self::findFile($class)) {
83
+ // 非 Win 环境不严格区分大小写
84
+ if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) {
85
+ __include_file($file);
86
+ return true;
87
+ }
88
+ }
89
+
90
+ return false;
91
+ }
92
+
93
+ /**
94
+ * 查找文件
95
+ * @access private
96
+ * @param string $class 类名
97
+ * @return bool|string
98
+ */
99
+ private static function findFile($class)
100
+ {
101
+ // 类库映射
102
+ if (!empty(self::$classMap[$class])) {
103
+ return self::$classMap[$class];
104
+ }
105
+
106
+ // 查找 PSR-4
107
+ $logicalPathPsr4 = strtr($class, '\\', DS) . EXT;
108
+ $first = $class[0];
109
+
110
+ if (isset(self::$prefixLengthsPsr4[$first])) {
111
+ foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
112
+ if (0 === strpos($class, $prefix)) {
113
+ foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
114
+ if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) {
115
+ return $file;
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ // 查找 PSR-4 fallback dirs
123
+ foreach (self::$fallbackDirsPsr4 as $dir) {
124
+ if (is_file($file = $dir . DS . $logicalPathPsr4)) {
125
+ return $file;
126
+ }
127
+ }
128
+
129
+ // 查找 PSR-0
130
+ if (false !== $pos = strrpos($class, '\\')) {
131
+ // namespace class name
132
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
133
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS);
134
+ } else {
135
+ // PEAR-like class name
136
+ $logicalPathPsr0 = strtr($class, '_', DS) . EXT;
137
+ }
138
+
139
+ if (isset(self::$prefixesPsr0[$first])) {
140
+ foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) {
141
+ if (0 === strpos($class, $prefix)) {
142
+ foreach ($dirs as $dir) {
143
+ if (is_file($file = $dir . DS . $logicalPathPsr0)) {
144
+ return $file;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // 查找 PSR-0 fallback dirs
152
+ foreach (self::$fallbackDirsPsr0 as $dir) {
153
+ if (is_file($file = $dir . DS . $logicalPathPsr0)) {
154
+ return $file;
155
+ }
156
+ }
157
+
158
+ // 找不到则设置映射为 false 并返回
159
+ return self::$classMap[$class] = false;
160
+ }
161
+
162
+ /**
163
+ * 注册 classmap
164
+ * @access public
165
+ * @param string|array $class 类名
166
+ * @param string $map 映射
167
+ * @return void
168
+ */
169
+ public static function addClassMap($class, $map = '')
170
+ {
171
+ if (is_array($class)) {
172
+ self::$classMap = array_merge(self::$classMap, $class);
173
+ } else {
174
+ self::$classMap[$class] = $map;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * 注册命名空间
180
+ * @access public
181
+ * @param string|array $namespace 命名空间
182
+ * @param string $path 路径
183
+ * @return void
184
+ */
185
+ public static function addNamespace($namespace, $path = '')
186
+ {
187
+ if (is_array($namespace)) {
188
+ foreach ($namespace as $prefix => $paths) {
189
+ self::addPsr4($prefix . '\\', rtrim($paths, DS), true);
190
+ }
191
+ } else {
192
+ self::addPsr4($namespace . '\\', rtrim($path, DS), true);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * 添加 PSR-0 命名空间
198
+ * @access private
199
+ * @param array|string $prefix 空间前缀
200
+ * @param array $paths 路径
201
+ * @param bool $prepend 预先设置的优先级更高
202
+ * @return void
203
+ */
204
+ private static function addPsr0($prefix, $paths, $prepend = false)
205
+ {
206
+ if (!$prefix) {
207
+ self::$fallbackDirsPsr0 = $prepend ?
208
+ array_merge((array) $paths, self::$fallbackDirsPsr0) :
209
+ array_merge(self::$fallbackDirsPsr0, (array) $paths);
210
+ } else {
211
+ $first = $prefix[0];
212
+
213
+ if (!isset(self::$prefixesPsr0[$first][$prefix])) {
214
+ self::$prefixesPsr0[$first][$prefix] = (array) $paths;
215
+ } else {
216
+ self::$prefixesPsr0[$first][$prefix] = $prepend ?
217
+ array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) :
218
+ array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths);
219
+ }
220
+ }
221
+ }
222
+
223
+ /**
224
+ * 添加 PSR-4 空间
225
+ * @access private
226
+ * @param array|string $prefix 空间前缀
227
+ * @param string $paths 路径
228
+ * @param bool $prepend 预先设置的优先级更高
229
+ * @return void
230
+ */
231
+ private static function addPsr4($prefix, $paths, $prepend = false)
232
+ {
233
+ if (!$prefix) {
234
+ // Register directories for the root namespace.
235
+ self::$fallbackDirsPsr4 = $prepend ?
236
+ array_merge((array) $paths, self::$fallbackDirsPsr4) :
237
+ array_merge(self::$fallbackDirsPsr4, (array) $paths);
238
+
239
+ } elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
240
+ // Register directories for a new namespace.
241
+ $length = strlen($prefix);
242
+ if ('\\' !== $prefix[$length - 1]) {
243
+ throw new \InvalidArgumentException(
244
+ "A non-empty PSR-4 prefix must end with a namespace separator."
245
+ );
246
+ }
247
+
248
+ self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
249
+ self::$prefixDirsPsr4[$prefix] = (array) $paths;
250
+
251
+ } else {
252
+ self::$prefixDirsPsr4[$prefix] = $prepend ?
253
+ // Prepend directories for an already registered namespace.
254
+ array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) :
255
+ // Append directories for an already registered namespace.
256
+ array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * 注册命名空间别名
262
+ * @access public
263
+ * @param array|string $namespace 命名空间
264
+ * @param string $original 源文件
265
+ * @return void
266
+ */
267
+ public static function addNamespaceAlias($namespace, $original = '')
268
+ {
269
+ if (is_array($namespace)) {
270
+ self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace);
271
+ } else {
272
+ self::$namespaceAlias[$namespace] = $original;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * 注册自动加载机制
278
+ * @access public
279
+ * @param callable $autoload 自动加载处理方法
280
+ * @return void
281
+ */
282
+ public static function register($autoload = null)
283
+ {
284
+ // 注册系统自动加载
285
+ spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
286
+
287
+ // Composer 自动加载支持
288
+ if (is_dir(VENDOR_PATH . 'composer')) {
289
+ if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) {
290
+ require VENDOR_PATH . 'composer' . DS . 'autoload_static.php';
291
+
292
+ $declaredClass = get_declared_classes();
293
+ $composerClass = array_pop($declaredClass);
294
+
295
+ foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
296
+ if (property_exists($composerClass, $attr)) {
297
+ self::${$attr} = $composerClass::${$attr};
298
+ }
299
+ }
300
+ } else {
301
+ self::registerComposerLoader();
302
+ }
303
+ }
304
+
305
+ // 注册命名空间定义
306
+ self::addNamespace([
307
+ 'think' => LIB_PATH . 'think' . DS,
308
+ 'behavior' => LIB_PATH . 'behavior' . DS,
309
+ 'traits' => LIB_PATH . 'traits' . DS,
310
+ ]);
311
+
312
+ // 加载类库映射文件
313
+ if (is_file(RUNTIME_PATH . 'classmap' . EXT)) {
314
+ self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT));
315
+ }
316
+
317
+ self::loadComposerAutoloadFiles();
318
+
319
+ // 自动加载 extend 目录
320
+ self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS);
321
+ }
322
+
323
+ /**
324
+ * 注册 composer 自动加载
325
+ * @access private
326
+ * @return void
327
+ */
328
+ private static function registerComposerLoader()
329
+ {
330
+ if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) {
331
+ $map = require VENDOR_PATH . 'composer/autoload_namespaces.php';
332
+ foreach ($map as $namespace => $path) {
333
+ self::addPsr0($namespace, $path);
334
+ }
335
+ }
336
+
337
+ if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) {
338
+ $map = require VENDOR_PATH . 'composer/autoload_psr4.php';
339
+ foreach ($map as $namespace => $path) {
340
+ self::addPsr4($namespace, $path);
341
+ }
342
+ }
343
+
344
+ if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) {
345
+ $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php';
346
+ if ($classMap) {
347
+ self::addClassMap($classMap);
348
+ }
349
+ }
350
+
351
+ if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) {
352
+ self::$files = require VENDOR_PATH . 'composer/autoload_files.php';
353
+ }
354
+ }
355
+
356
+ // 加载composer autofile文件
357
+ public static function loadComposerAutoloadFiles()
358
+ {
359
+ foreach (self::$files as $fileIdentifier => $file) {
360
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
361
+ __require_file($file);
362
+
363
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
364
+ }
365
+ }
366
+ }
367
+
368
+ /**
369
+ * 导入所需的类库 同 Java 的 Import 本函数有缓存功能
370
+ * @access public
371
+ * @param string $class 类库命名空间字符串
372
+ * @param string $baseUrl 起始路径
373
+ * @param string $ext 导入的文件扩展名
374
+ * @return bool
375
+ */
376
+ public static function import($class, $baseUrl = '', $ext = EXT)
377
+ {
378
+ static $_file = [];
379
+ $key = $class . $baseUrl;
380
+ $class = str_replace(['.', '#'], [DS, '.'], $class);
381
+
382
+ if (isset($_file[$key])) {
383
+ return true;
384
+ }
385
+
386
+ if (empty($baseUrl)) {
387
+ list($name, $class) = explode(DS, $class, 2);
388
+
389
+ if (isset(self::$prefixDirsPsr4[$name . '\\'])) {
390
+ // 注册的命名空间
391
+ $baseUrl = self::$prefixDirsPsr4[$name . '\\'];
392
+ } elseif ('@' == $name) {
393
+ // 加载当前模块应用类库
394
+ $baseUrl = App::$modulePath;
395
+ } elseif (is_dir(EXTEND_PATH . $name)) {
396
+ $baseUrl = EXTEND_PATH . $name . DS;
397
+ } else {
398
+ // 加载其它模块的类库
399
+ $baseUrl = APP_PATH . $name . DS;
400
+ }
401
+ } elseif (substr($baseUrl, -1) != DS) {
402
+ $baseUrl .= DS;
403
+ }
404
+
405
+ // 如果类存在则导入类库文件
406
+ if (is_array($baseUrl)) {
407
+ foreach ($baseUrl as $path) {
408
+ if (is_file($filename = $path . DS . $class . $ext)) {
409
+ break;
410
+ }
411
+ }
412
+ } else {
413
+ $filename = $baseUrl . $class . $ext;
414
+ }
415
+
416
+ if (!empty($filename) &&
417
+ is_file($filename) &&
418
+ (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME))
419
+ ) {
420
+ __include_file($filename);
421
+ $_file[$key] = true;
422
+
423
+ return true;
424
+ }
425
+
426
+ return false;
427
+ }
428
+
429
+ /**
430
+ * 实例化(分层)模型
431
+ * @access public
432
+ * @param string $name Model名称
433
+ * @param string $layer 业务层名称
434
+ * @param bool $appendSuffix 是否添加类名后缀
435
+ * @param string $common 公共模块名
436
+ * @return object
437
+ * @throws ClassNotFoundException
438
+ */
439
+ public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
440
+ {
441
+ $uid = $name . $layer;
442
+
443
+ if (isset(self::$instance[$uid])) {
444
+ return self::$instance[$uid];
445
+ }
446
+
447
+ list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
448
+
449
+ if (class_exists($class)) {
450
+ $model = new $class();
451
+ } else {
452
+ $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
453
+
454
+ if (class_exists($class)) {
455
+ $model = new $class();
456
+ } else {
457
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
458
+ }
459
+ }
460
+
461
+ return self::$instance[$uid] = $model;
462
+ }
463
+
464
+ /**
465
+ * 实例化(分层)控制器 格式:[模块名/]控制器名
466
+ * @access public
467
+ * @param string $name 资源地址
468
+ * @param string $layer 控制层名称
469
+ * @param bool $appendSuffix 是否添加类名后缀
470
+ * @param string $empty 空控制器名称
471
+ * @return object
472
+ * @throws ClassNotFoundException
473
+ */
474
+ public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
475
+ {
476
+ list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
477
+
478
+ if (class_exists($class)) {
479
+ return App::invokeClass($class);
480
+ }
481
+
482
+ if ($empty) {
483
+ $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
484
+
485
+ if (class_exists($emptyClass)) {
486
+ return new $emptyClass(Request::instance());
487
+ }
488
+ }
489
+
490
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
491
+ }
492
+
493
+ /**
494
+ * 实例化验证类 格式:[模块名/]验证器名
495
+ * @access public
496
+ * @param string $name 资源地址
497
+ * @param string $layer 验证层名称
498
+ * @param bool $appendSuffix 是否添加类名后缀
499
+ * @param string $common 公共模块名
500
+ * @return object|false
501
+ * @throws ClassNotFoundException
502
+ */
503
+ public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common')
504
+ {
505
+ $name = $name ?: Config::get('default_validate');
506
+
507
+ if (empty($name)) {
508
+ return new Validate;
509
+ }
510
+
511
+ $uid = $name . $layer;
512
+ if (isset(self::$instance[$uid])) {
513
+ return self::$instance[$uid];
514
+ }
515
+
516
+ list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
517
+
518
+ if (class_exists($class)) {
519
+ $validate = new $class;
520
+ } else {
521
+ $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
522
+
523
+ if (class_exists($class)) {
524
+ $validate = new $class;
525
+ } else {
526
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
527
+ }
528
+ }
529
+
530
+ return self::$instance[$uid] = $validate;
531
+ }
532
+
533
+ /**
534
+ * 解析模块和类名
535
+ * @access protected
536
+ * @param string $name 资源地址
537
+ * @param string $layer 验证层名称
538
+ * @param bool $appendSuffix 是否添加类名后缀
539
+ * @return array
540
+ */
541
+ protected static function getModuleAndClass($name, $layer, $appendSuffix)
542
+ {
543
+ if (false !== strpos($name, '\\')) {
544
+ $module = Request::instance()->module();
545
+ $class = $name;
546
+ } else {
547
+ if (strpos($name, '/')) {
548
+ list($module, $name) = explode('/', $name, 2);
549
+ } else {
550
+ $module = Request::instance()->module();
551
+ }
552
+
553
+ $class = self::parseClass($module, $layer, $name, $appendSuffix);
554
+ }
555
+
556
+ return [$module, $class];
557
+ }
558
+
559
+ /**
560
+ * 数据库初始化 并取得数据库类实例
561
+ * @access public
562
+ * @param mixed $config 数据库配置
563
+ * @param bool|string $name 连接标识 true 强制重新连接
564
+ * @return \think\db\Connection
565
+ */
566
+ public static function db($config = [], $name = false)
567
+ {
568
+ return Db::connect($config, $name);
569
+ }
570
+
571
+ /**
572
+ * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作
573
+ * @access public
574
+ * @param string $url 调用地址
575
+ * @param string|array $vars 调用参数 支持字符串和数组
576
+ * @param string $layer 要调用的控制层名称
577
+ * @param bool $appendSuffix 是否添加类名后缀
578
+ * @return mixed
579
+ */
580
+ public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
581
+ {
582
+ $info = pathinfo($url);
583
+ $action = $info['basename'];
584
+ $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller();
585
+ $class = self::controller($module, $layer, $appendSuffix);
586
+
587
+ if ($class) {
588
+ if (is_scalar($vars)) {
589
+ if (strpos($vars, '=')) {
590
+ parse_str($vars, $vars);
591
+ } else {
592
+ $vars = [$vars];
593
+ }
594
+ }
595
+
596
+ return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars);
597
+ }
598
+
599
+ return false;
600
+ }
601
+
602
+ /**
603
+ * 字符串命名风格转换
604
+ * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格
605
+ * @access public
606
+ * @param string $name 字符串
607
+ * @param integer $type 转换类型
608
+ * @param bool $ucfirst 首字母是否大写(驼峰��则)
609
+ * @return string
610
+ */
611
+ public static function parseName($name, $type = 0, $ucfirst = true)
612
+ {
613
+ if ($type) {
614
+ $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
615
+ return strtoupper($match[1]);
616
+ }, $name);
617
+
618
+ return $ucfirst ? ucfirst($name) : lcfirst($name);
619
+ }
620
+
621
+ return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
622
+ }
623
+
624
+ /**
625
+ * 解析应用类的类名
626
+ * @access public
627
+ * @param string $module 模块名
628
+ * @param string $layer 层名 controller model ...
629
+ * @param string $name 类名
630
+ * @param bool $appendSuffix 是否添加类名后缀
631
+ * @return string
632
+ */
633
+ public static function parseClass($module, $layer, $name, $appendSuffix = false)
634
+ {
635
+
636
+ $array = explode('\\', str_replace(['/', '.'], '\\', $name));
637
+ $class = self::parseName(array_pop($array), 1);
638
+ $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : '');
639
+ $path = $array ? implode('\\', $array) . '\\' : '';
640
+
641
+ return App::$namespace . '\\' .
642
+ ($module ? $module . '\\' : '') .
643
+ $layer . '\\' . $path . $class;
644
+ }
645
+
646
+ /**
647
+ * 初始化类的实例
648
+ * @access public
649
+ * @return void
650
+ */
651
+ public static function clearInstance()
652
+ {
653
+ self::$instance = [];
654
+ }
655
+ }
656
+
657
+ // 作用范围隔离
658
+
659
+ /**
660
+ * include
661
+ * @param string $file 文件路径
662
+ * @return mixed
663
+ */
664
+ function __include_file($file)
665
+ {
666
+ return include $file;
667
+ }
668
+
669
+ /**
670
+ * require
671
+ * @param string $file 文件路径
672
+ * @return mixed
673
+ */
674
+ function __require_file($file)
675
+ {
676
+ return require $file;
677
+ }
thinkphp/library/think/Log.php ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\ClassNotFoundException;
15
+
16
+ /**
17
+ * Class Log
18
+ * @package think
19
+ *
20
+ * @method void log($msg) static 记录一般日志
21
+ * @method void error($msg) static 记录错误日志
22
+ * @method void info($msg) static 记录一般信息日志
23
+ * @method void sql($msg) static 记录 SQL 查询日志
24
+ * @method void notice($msg) static 记录提示日志
25
+ * @method void alert($msg) static 记录报警日志
26
+ */
27
+ class Log
28
+ {
29
+ const LOG = 'log';
30
+ const ERROR = 'error';
31
+ const INFO = 'info';
32
+ const SQL = 'sql';
33
+ const NOTICE = 'notice';
34
+ const ALERT = 'alert';
35
+ const DEBUG = 'debug';
36
+
37
+ /**
38
+ * @var array 日志信息
39
+ */
40
+ protected static $log = [];
41
+
42
+ /**
43
+ * @var array 配置参数
44
+ */
45
+ protected static $config = [];
46
+
47
+ /**
48
+ * @var array 日志类型
49
+ */
50
+ protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug'];
51
+
52
+ /**
53
+ * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动
54
+ */
55
+ protected static $driver;
56
+
57
+ /**
58
+ * @var string 当前日志授权 key
59
+ */
60
+ protected static $key;
61
+
62
+ /**
63
+ * 日志初始化
64
+ * @access public
65
+ * @param array $config 配置参数
66
+ * @return void
67
+ */
68
+ public static function init($config = [])
69
+ {
70
+ $type = isset($config['type']) ? $config['type'] : 'File';
71
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type);
72
+
73
+ self::$config = $config;
74
+ unset($config['type']);
75
+
76
+ if (class_exists($class)) {
77
+ self::$driver = new $class($config);
78
+ } else {
79
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
80
+ }
81
+
82
+ // 记录初始化信息
83
+ App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info');
84
+ }
85
+
86
+ /**
87
+ * 获取日志信息
88
+ * @access public
89
+ * @param string $type 信息类型
90
+ * @return array|string
91
+ */
92
+ public static function getLog($type = '')
93
+ {
94
+ return $type ? self::$log[$type] : self::$log;
95
+ }
96
+
97
+ /**
98
+ * 记录调试信息
99
+ * @access public
100
+ * @param mixed $msg 调试信息
101
+ * @param string $type 信息类型
102
+ * @return void
103
+ */
104
+ public static function record($msg, $type = 'log')
105
+ {
106
+ self::$log[$type][] = $msg;
107
+
108
+ // 命令行下面日志写入改进
109
+ IS_CLI && self::save();
110
+ }
111
+
112
+ /**
113
+ * 清空日志信息
114
+ * @access public
115
+ * @return void
116
+ */
117
+ public static function clear()
118
+ {
119
+ self::$log = [];
120
+ }
121
+
122
+ /**
123
+ * 设置当前日志记录的授权 key
124
+ * @access public
125
+ * @param string $key 授权 key
126
+ * @return void
127
+ */
128
+ public static function key($key)
129
+ {
130
+ self::$key = $key;
131
+ }
132
+
133
+ /**
134
+ * 检查日志写入权限
135
+ * @access public
136
+ * @param array $config 当前日志配置参数
137
+ * @return bool
138
+ */
139
+ public static function check($config)
140
+ {
141
+ return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']);
142
+ }
143
+
144
+ /**
145
+ * 保存调试信息
146
+ * @access public
147
+ * @return bool
148
+ */
149
+ public static function save()
150
+ {
151
+ // 没有需要保存的记录则直接返回
152
+ if (empty(self::$log)) {
153
+ return true;
154
+ }
155
+
156
+ is_null(self::$driver) && self::init(Config::get('log'));
157
+
158
+ // 检测日志写入权限
159
+ if (!self::check(self::$config)) {
160
+ return false;
161
+ }
162
+
163
+ if (empty(self::$config['level'])) {
164
+ // 获取全部日志
165
+ $log = self::$log;
166
+ if (!App::$debug && isset($log['debug'])) {
167
+ unset($log['debug']);
168
+ }
169
+ } else {
170
+ // 记录允许级别
171
+ $log = [];
172
+ foreach (self::$config['level'] as $level) {
173
+ if (isset(self::$log[$level])) {
174
+ $log[$level] = self::$log[$level];
175
+ }
176
+ }
177
+ }
178
+
179
+ if ($result = self::$driver->save($log, true)) {
180
+ self::$log = [];
181
+ }
182
+
183
+ Hook::listen('log_write_done', $log);
184
+
185
+ return $result;
186
+ }
187
+
188
+ /**
189
+ * 实时写入日志信息 并支持行为
190
+ * @access public
191
+ * @param mixed $msg 调试信息
192
+ * @param string $type 信息类型
193
+ * @param bool $force 是否强制写入
194
+ * @return bool
195
+ */
196
+ public static function write($msg, $type = 'log', $force = false)
197
+ {
198
+ $log = self::$log;
199
+
200
+ // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录
201
+ if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) {
202
+ return false;
203
+ }
204
+
205
+ // 封装日志信息
206
+ $log[$type][] = $msg;
207
+
208
+ // 监听 log_write
209
+ Hook::listen('log_write', $log);
210
+
211
+ is_null(self::$driver) && self::init(Config::get('log'));
212
+
213
+ // 写入日志
214
+ if ($result = self::$driver->save($log, false)) {
215
+ self::$log = [];
216
+ }
217
+
218
+ return $result;
219
+ }
220
+
221
+ /**
222
+ * 静态方法调用
223
+ * @access public
224
+ * @param string $method 调用方法
225
+ * @param mixed $args 参数
226
+ * @return void
227
+ */
228
+ public static function __callStatic($method, $args)
229
+ {
230
+ if (in_array($method, self::$type)) {
231
+ array_push($args, $method);
232
+
233
+ call_user_func_array('\\think\\Log::record', $args);
234
+ }
235
+ }
236
+
237
+ }
thinkphp/library/think/Model.php ADDED
@@ -0,0 +1,2350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use BadMethodCallException;
15
+ use InvalidArgumentException;
16
+ use think\db\Query;
17
+ use think\exception\ValidateException;
18
+ use think\model\Collection as ModelCollection;
19
+ use think\model\Relation;
20
+ use think\model\relation\BelongsTo;
21
+ use think\model\relation\BelongsToMany;
22
+ use think\model\relation\HasMany;
23
+ use think\model\relation\HasManyThrough;
24
+ use think\model\relation\HasOne;
25
+ use think\model\relation\MorphMany;
26
+ use think\model\relation\MorphOne;
27
+ use think\model\relation\MorphTo;
28
+
29
+ /**
30
+ * Class Model
31
+ * @package think
32
+ * @mixin Query
33
+ */
34
+ abstract class Model implements \JsonSerializable, \ArrayAccess
35
+ {
36
+ // 数据库查询对象池
37
+ protected static $links = [];
38
+ // 数据库配置
39
+ protected $connection = [];
40
+ // 父关联模型对象
41
+ protected $parent;
42
+ // 数据库查询对象
43
+ protected $query;
44
+ // 当前模型名称
45
+ protected $name;
46
+ // 数据表名称
47
+ protected $table;
48
+ // 当前类名称
49
+ protected $class;
50
+ // 回调事件
51
+ private static $event = [];
52
+ // 错误信息
53
+ protected $error;
54
+ // 字段验证规则
55
+ protected $validate;
56
+ // 数据表主键 复合主键使用数组定义 不设置则自动获取
57
+ protected $pk;
58
+ // 数据表字段信息 留空则自动获取
59
+ protected $field = [];
60
+ // 数据排除字段
61
+ protected $except = [];
62
+ // 数据废弃字段
63
+ protected $disuse = [];
64
+ // 只读字段
65
+ protected $readonly = [];
66
+ // 显示属性
67
+ protected $visible = [];
68
+ // 隐藏属性
69
+ protected $hidden = [];
70
+ // 追加属性
71
+ protected $append = [];
72
+ // 数据信息
73
+ protected $data = [];
74
+ // 原始数据
75
+ protected $origin = [];
76
+ // 关联模型
77
+ protected $relation = [];
78
+
79
+ // 保存自动完成列表
80
+ protected $auto = [];
81
+ // 新增自动完成列表
82
+ protected $insert = [];
83
+ // 更新自动完成列表
84
+ protected $update = [];
85
+ // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型
86
+ protected $autoWriteTimestamp;
87
+ // 创建时间字段
88
+ protected $createTime = 'create_time';
89
+ // 更新时间字段
90
+ protected $updateTime = 'update_time';
91
+ // 时间字段取出后的默认时间格式
92
+ protected $dateFormat;
93
+ // 字段类型或者格式转换
94
+ protected $type = [];
95
+ // 是否为更新数据
96
+ protected $isUpdate = false;
97
+ // 是否使用Replace
98
+ protected $replace = false;
99
+ // 是否强制更新所有数据
100
+ protected $force = false;
101
+ // 更新条件
102
+ protected $updateWhere;
103
+ // 验证失败是否抛出异常
104
+ protected $failException = false;
105
+ // 全局查询范围
106
+ protected $useGlobalScope = true;
107
+ // 是否采用批量验证
108
+ protected $batchValidate = false;
109
+ // 查询数据集对象
110
+ protected $resultSetType;
111
+ // 关联自动写入
112
+ protected $relationWrite;
113
+
114
+ /**
115
+ * 初始化过的模型.
116
+ *
117
+ * @var array
118
+ */
119
+ protected static $initialized = [];
120
+
121
+ /**
122
+ * 是否从主库读取(主从分布式有效)
123
+ * @var array
124
+ */
125
+ protected static $readMaster;
126
+
127
+ /**
128
+ * 构造方法
129
+ * @access public
130
+ * @param array|object $data 数据
131
+ */
132
+ public function __construct($data = [])
133
+ {
134
+ if (is_object($data)) {
135
+ $this->data = get_object_vars($data);
136
+ } else {
137
+ $this->data = $data;
138
+ }
139
+
140
+ if ($this->disuse) {
141
+ // 废弃字段
142
+ foreach ((array) $this->disuse as $key) {
143
+ if (array_key_exists($key, $this->data)) {
144
+ unset($this->data[$key]);
145
+ }
146
+ }
147
+ }
148
+
149
+ // 记录原始数据
150
+ $this->origin = $this->data;
151
+
152
+ // 当前类名
153
+ $this->class = get_called_class();
154
+
155
+ if (empty($this->name)) {
156
+ // 当前模型名
157
+ $name = str_replace('\\', '/', $this->class);
158
+ $this->name = basename($name);
159
+ if (Config::get('class_suffix')) {
160
+ $suffix = basename(dirname($name));
161
+ $this->name = substr($this->name, 0, -strlen($suffix));
162
+ }
163
+ }
164
+
165
+ if (is_null($this->autoWriteTimestamp)) {
166
+ // 自动写入时间戳
167
+ $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp');
168
+ }
169
+
170
+ if (is_null($this->dateFormat)) {
171
+ // 设置时间戳格式
172
+ $this->dateFormat = $this->getQuery()->getConfig('datetime_format');
173
+ }
174
+
175
+ if (is_null($this->resultSetType)) {
176
+ $this->resultSetType = $this->getQuery()->getConfig('resultset_type');
177
+ }
178
+ // 执行初始化操作
179
+ $this->initialize();
180
+ }
181
+
182
+ /**
183
+ * 是否从主库读取数据(主从分布有效)
184
+ * @access public
185
+ * @param bool $all 是否所有模型生效
186
+ * @return $this
187
+ */
188
+ public function readMaster($all = false)
189
+ {
190
+ $model = $all ? '*' : $this->class;
191
+
192
+ static::$readMaster[$model] = true;
193
+ return $this;
194
+ }
195
+
196
+ /**
197
+ * 创建模型的查询对象
198
+ * @access protected
199
+ * @return Query
200
+ */
201
+ protected function buildQuery()
202
+ {
203
+ // 合并数据库配置
204
+ if (!empty($this->connection)) {
205
+ if (is_array($this->connection)) {
206
+ $connection = array_merge(Config::get('database'), $this->connection);
207
+ } else {
208
+ $connection = $this->connection;
209
+ }
210
+ } else {
211
+ $connection = [];
212
+ }
213
+
214
+ $con = Db::connect($connection);
215
+ // 设置当前模型 确保查询返回模型对象
216
+ $queryClass = $this->query ?: $con->getConfig('query');
217
+ $query = new $queryClass($con, $this);
218
+
219
+ if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) {
220
+ $query->master(true);
221
+ }
222
+
223
+ // 设置当前数据表和模型名
224
+ if (!empty($this->table)) {
225
+ $query->setTable($this->table);
226
+ } else {
227
+ $query->name($this->name);
228
+ }
229
+
230
+ if (!empty($this->pk)) {
231
+ $query->pk($this->pk);
232
+ }
233
+
234
+ return $query;
235
+ }
236
+
237
+ /**
238
+ * 创建新的模型实例
239
+ * @access public
240
+ * @param array|object $data 数据
241
+ * @param bool $isUpdate 是否为更新
242
+ * @param mixed $where 更新条件
243
+ * @return Model
244
+ */
245
+ public function newInstance($data = [], $isUpdate = false, $where = null)
246
+ {
247
+ return (new static($data))->isUpdate($isUpdate, $where);
248
+ }
249
+
250
+ /**
251
+ * 获取当前模型的查询对象
252
+ * @access public
253
+ * @param bool $buildNewQuery 创建新的查询对象
254
+ * @return Query
255
+ */
256
+ public function getQuery($buildNewQuery = false)
257
+ {
258
+ if ($buildNewQuery) {
259
+ return $this->buildQuery();
260
+ } elseif (!isset(self::$links[$this->class])) {
261
+ // 创建模型查询对象
262
+ self::$links[$this->class] = $this->buildQuery();
263
+ }
264
+
265
+ return self::$links[$this->class];
266
+ }
267
+
268
+ /**
269
+ * 获取当前模型的数据库查询对象
270
+ * @access public
271
+ * @param bool $useBaseQuery 是否调用全局查询范围
272
+ * @param bool $buildNewQuery 创建新的查询对象
273
+ * @return Query
274
+ */
275
+ public function db($useBaseQuery = true, $buildNewQuery = true)
276
+ {
277
+ $query = $this->getQuery($buildNewQuery);
278
+
279
+ // 全局作用域
280
+ if ($useBaseQuery && method_exists($this, 'base')) {
281
+ call_user_func_array([$this, 'base'], [ & $query]);
282
+ }
283
+
284
+ // 返回当前模型的数据库查询对象
285
+ return $query;
286
+ }
287
+
288
+ /**
289
+ * 初始化模型
290
+ * @access protected
291
+ * @return void
292
+ */
293
+ protected function initialize()
294
+ {
295
+ $class = get_class($this);
296
+ if (!isset(static::$initialized[$class])) {
297
+ static::$initialized[$class] = true;
298
+ static::init();
299
+ }
300
+ }
301
+
302
+ /**
303
+ * 初始化处理
304
+ * @access protected
305
+ * @return void
306
+ */
307
+ protected static function init()
308
+ {
309
+ }
310
+
311
+ /**
312
+ * 设置父关联对象
313
+ * @access public
314
+ * @param Model $model 模型对象
315
+ * @return $this
316
+ */
317
+ public function setParent($model)
318
+ {
319
+ $this->parent = $model;
320
+ return $this;
321
+ }
322
+
323
+ /**
324
+ * 获取父关联对象
325
+ * @access public
326
+ * @return Model
327
+ */
328
+ public function getParent()
329
+ {
330
+ return $this->parent;
331
+ }
332
+
333
+ /**
334
+ * 设置数据对象值
335
+ * @access public
336
+ * @param mixed $data 数据或者属性名
337
+ * @param mixed $value 值
338
+ * @return $this
339
+ */
340
+ public function data($data, $value = null)
341
+ {
342
+ if (is_string($data)) {
343
+ $this->data[$data] = $value;
344
+ } else {
345
+ // 清空数据
346
+ $this->data = [];
347
+ if (is_object($data)) {
348
+ $data = get_object_vars($data);
349
+ }
350
+ if (true === $value) {
351
+ // 数据对象赋值
352
+ foreach ($data as $key => $value) {
353
+ $this->setAttr($key, $value, $data);
354
+ }
355
+ } else {
356
+ $this->data = $data;
357
+ }
358
+ }
359
+ return $this;
360
+ }
361
+
362
+ /**
363
+ * 获取对象原始数据 如果不存在指定字段返回false
364
+ * @access public
365
+ * @param string $name 字段名 留空获取全部
366
+ * @return mixed
367
+ * @throws InvalidArgumentException
368
+ */
369
+ public function getData($name = null)
370
+ {
371
+ if (is_null($name)) {
372
+ return $this->data;
373
+ } elseif (array_key_exists($name, $this->data)) {
374
+ return $this->data[$name];
375
+ } elseif (array_key_exists($name, $this->relation)) {
376
+ return $this->relation[$name];
377
+ } else {
378
+ throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name);
379
+ }
380
+ }
381
+
382
+ /**
383
+ * 是否需要自动写入时间字段
384
+ * @access public
385
+ * @param bool $auto
386
+ * @return $this
387
+ */
388
+ public function isAutoWriteTimestamp($auto)
389
+ {
390
+ $this->autoWriteTimestamp = $auto;
391
+ return $this;
392
+ }
393
+
394
+ /**
395
+ * 更新是否强制写入数据 而不做比较
396
+ * @access public
397
+ * @param bool $force
398
+ * @return $this
399
+ */
400
+ public function force($force = true)
401
+ {
402
+ $this->force = $force;
403
+ return $this;
404
+ }
405
+
406
+ /**
407
+ * 修改器 设置数据对象值
408
+ * @access public
409
+ * @param string $name 属性名
410
+ * @param mixed $value 属性值
411
+ * @param array $data 数据
412
+ * @return $this
413
+ */
414
+ public function setAttr($name, $value, $data = [])
415
+ {
416
+ if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
417
+ // 自动写入的时间戳字段
418
+ $value = $this->autoWriteTimestamp($name);
419
+ } else {
420
+ // 检测修改器
421
+ $method = 'set' . Loader::parseName($name, 1) . 'Attr';
422
+ if (method_exists($this, $method)) {
423
+ $value = $this->$method($value, array_merge($this->data, $data), $this->relation);
424
+ } elseif (isset($this->type[$name])) {
425
+ // 类型转换
426
+ $value = $this->writeTransform($value, $this->type[$name]);
427
+ }
428
+ }
429
+
430
+ // 设置数据对象属性
431
+ $this->data[$name] = $value;
432
+ return $this;
433
+ }
434
+
435
+ /**
436
+ * 获取当前模型的关联模型数据
437
+ * @access public
438
+ * @param string $name 关联方法名
439
+ * @return mixed
440
+ */
441
+ public function getRelation($name = null)
442
+ {
443
+ if (is_null($name)) {
444
+ return $this->relation;
445
+ } elseif (array_key_exists($name, $this->relation)) {
446
+ return $this->relation[$name];
447
+ } else {
448
+ return;
449
+ }
450
+ }
451
+
452
+ /**
453
+ * 设置关联数据对象值
454
+ * @access public
455
+ * @param string $name 属性名
456
+ * @param mixed $value 属性值
457
+ * @return $this
458
+ */
459
+ public function setRelation($name, $value)
460
+ {
461
+ $this->relation[$name] = $value;
462
+ return $this;
463
+ }
464
+
465
+ /**
466
+ * 自动写入时间戳
467
+ * @access public
468
+ * @param string $name 时间戳字段
469
+ * @return mixed
470
+ */
471
+ protected function autoWriteTimestamp($name)
472
+ {
473
+ if (isset($this->type[$name])) {
474
+ $type = $this->type[$name];
475
+ if (strpos($type, ':')) {
476
+ list($type, $param) = explode(':', $type, 2);
477
+ }
478
+ switch ($type) {
479
+ case 'datetime':
480
+ case 'date':
481
+ $format = !empty($param) ? $param : $this->dateFormat;
482
+ $value = $this->formatDateTime(time(), $format);
483
+ break;
484
+ case 'timestamp':
485
+ case 'integer':
486
+ default:
487
+ $value = time();
488
+ break;
489
+ }
490
+ } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
491
+ 'datetime',
492
+ 'date',
493
+ 'timestamp',
494
+ ])
495
+ ) {
496
+ $value = $this->formatDateTime(time(), $this->dateFormat);
497
+ } else {
498
+ $value = $this->formatDateTime(time(), $this->dateFormat, true);
499
+ }
500
+ return $value;
501
+ }
502
+
503
+ /**
504
+ * 时间日期字段格式化处理
505
+ * @access public
506
+ * @param mixed $time 时间日期表达式
507
+ * @param mixed $format 日期格式
508
+ * @param bool $timestamp 是否进行时间戳转换
509
+ * @return mixed
510
+ */
511
+ protected function formatDateTime($time, $format, $timestamp = false)
512
+ {
513
+ if (false !== strpos($format, '\\')) {
514
+ $time = new $format($time);
515
+ } elseif (!$timestamp && false !== $format) {
516
+ $time = date($format, $time);
517
+ }
518
+ return $time;
519
+ }
520
+
521
+ /**
522
+ * 数据写入 类型转换
523
+ * @access public
524
+ * @param mixed $value 值
525
+ * @param string|array $type 要��换的类型
526
+ * @return mixed
527
+ */
528
+ protected function writeTransform($value, $type)
529
+ {
530
+ if (is_null($value)) {
531
+ return;
532
+ }
533
+
534
+ if (is_array($type)) {
535
+ list($type, $param) = $type;
536
+ } elseif (strpos($type, ':')) {
537
+ list($type, $param) = explode(':', $type, 2);
538
+ }
539
+ switch ($type) {
540
+ case 'integer':
541
+ $value = (int) $value;
542
+ break;
543
+ case 'float':
544
+ if (empty($param)) {
545
+ $value = (float) $value;
546
+ } else {
547
+ $value = (float) number_format($value, $param, '.', '');
548
+ }
549
+ break;
550
+ case 'boolean':
551
+ $value = (bool) $value;
552
+ break;
553
+ case 'timestamp':
554
+ if (!is_numeric($value)) {
555
+ $value = strtotime($value);
556
+ }
557
+ break;
558
+ case 'datetime':
559
+ $format = !empty($param) ? $param : $this->dateFormat;
560
+ $value = is_numeric($value) ? $value : strtotime($value);
561
+ $value = $this->formatDateTime($value, $format);
562
+ break;
563
+ case 'object':
564
+ if (is_object($value)) {
565
+ $value = json_encode($value, JSON_FORCE_OBJECT);
566
+ }
567
+ break;
568
+ case 'array':
569
+ $value = (array) $value;
570
+ case 'json':
571
+ $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;
572
+ $value = json_encode($value, $option);
573
+ break;
574
+ case 'serialize':
575
+ $value = serialize($value);
576
+ break;
577
+
578
+ }
579
+ return $value;
580
+ }
581
+
582
+ /**
583
+ * 获取器 获取数据对象的值
584
+ * @access public
585
+ * @param string $name 名称
586
+ * @return mixed
587
+ * @throws InvalidArgumentException
588
+ */
589
+ public function getAttr($name)
590
+ {
591
+ try {
592
+ $notFound = false;
593
+ $value = $this->getData($name);
594
+ } catch (InvalidArgumentException $e) {
595
+ $notFound = true;
596
+ $value = null;
597
+ }
598
+
599
+ // 检测属性获取器
600
+ $method = 'get' . Loader::parseName($name, 1) . 'Attr';
601
+ if (method_exists($this, $method)) {
602
+ $value = $this->$method($value, $this->data, $this->relation);
603
+ } elseif (isset($this->type[$name])) {
604
+ // 类型转换
605
+ $value = $this->readTransform($value, $this->type[$name]);
606
+ } elseif (in_array($name, [$this->createTime, $this->updateTime])) {
607
+ if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
608
+ 'datetime',
609
+ 'date',
610
+ 'timestamp',
611
+ ])
612
+ ) {
613
+ $value = $this->formatDateTime(strtotime($value), $this->dateFormat);
614
+ } else {
615
+ $value = $this->formatDateTime($value, $this->dateFormat);
616
+ }
617
+ } elseif ($notFound) {
618
+ $relation = Loader::parseName($name, 1, false);
619
+ if (method_exists($this, $relation)) {
620
+ $modelRelation = $this->$relation();
621
+ // 不存在该字段 获取关联数据
622
+ $value = $this->getRelationData($modelRelation);
623
+ // 保存关联对象值
624
+ $this->relation[$name] = $value;
625
+ } else {
626
+ throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name);
627
+ }
628
+ }
629
+ return $value;
630
+ }
631
+
632
+ /**
633
+ * 获取关联模型数据
634
+ * @access public
635
+ * @param Relation $modelRelation 模型关联对象
636
+ * @return mixed
637
+ * @throws BadMethodCallException
638
+ */
639
+ protected function getRelationData(Relation $modelRelation)
640
+ {
641
+ if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
642
+ $value = $this->parent;
643
+ } else {
644
+ // 首先获取关联数据
645
+ if (method_exists($modelRelation, 'getRelation')) {
646
+ $value = $modelRelation->getRelation();
647
+ } else {
648
+ throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
649
+ }
650
+ }
651
+ return $value;
652
+ }
653
+
654
+ /**
655
+ * 数据读取 类型转换
656
+ * @access public
657
+ * @param mixed $value 值
658
+ * @param string|array $type 要转换的类型
659
+ * @return mixed
660
+ */
661
+ protected function readTransform($value, $type)
662
+ {
663
+ if (is_null($value)) {
664
+ return;
665
+ }
666
+
667
+ if (is_array($type)) {
668
+ list($type, $param) = $type;
669
+ } elseif (strpos($type, ':')) {
670
+ list($type, $param) = explode(':', $type, 2);
671
+ }
672
+ switch ($type) {
673
+ case 'integer':
674
+ $value = (int) $value;
675
+ break;
676
+ case 'float':
677
+ if (empty($param)) {
678
+ $value = (float) $value;
679
+ } else {
680
+ $value = (float) number_format($value, $param, '.', '');
681
+ }
682
+ break;
683
+ case 'boolean':
684
+ $value = (bool) $value;
685
+ break;
686
+ case 'timestamp':
687
+ if (!is_null($value)) {
688
+ $format = !empty($param) ? $param : $this->dateFormat;
689
+ $value = $this->formatDateTime($value, $format);
690
+ }
691
+ break;
692
+ case 'datetime':
693
+ if (!is_null($value)) {
694
+ $format = !empty($param) ? $param : $this->dateFormat;
695
+ $value = $this->formatDateTime(strtotime($value), $format);
696
+ }
697
+ break;
698
+ case 'json':
699
+ $value = json_decode($value, true);
700
+ break;
701
+ case 'array':
702
+ $value = empty($value) ? [] : json_decode($value, true);
703
+ break;
704
+ case 'object':
705
+ $value = empty($value) ? new \stdClass() : json_decode($value);
706
+ break;
707
+ case 'serialize':
708
+ try {
709
+ $value = unserialize($value);
710
+ } catch (\Exception $e) {
711
+ $value = null;
712
+ }
713
+ break;
714
+ default:
715
+ if (false !== strpos($type, '\\')) {
716
+ // 对象类型
717
+ $value = new $type($value);
718
+ }
719
+ }
720
+ return $value;
721
+ }
722
+
723
+ /**
724
+ * 设置需要追加的输出属性
725
+ * @access public
726
+ * @param array $append 属性列表
727
+ * @param bool $override 是否覆盖
728
+ * @return $this
729
+ */
730
+ public function append($append = [], $override = false)
731
+ {
732
+ $this->append = $override ? $append : array_merge($this->append, $append);
733
+ return $this;
734
+ }
735
+
736
+ /**
737
+ * 设置附加关联对象的属性
738
+ * @access public
739
+ * @param string $relation 关联方法
740
+ * @param string|array $append 追加属性名
741
+ * @return $this
742
+ * @throws Exception
743
+ */
744
+ public function appendRelationAttr($relation, $append)
745
+ {
746
+ if (is_string($append)) {
747
+ $append = explode(',', $append);
748
+ }
749
+
750
+ $relation = Loader::parseName($relation, 1, false);
751
+
752
+ // 获取关联数据
753
+ if (isset($this->relation[$relation])) {
754
+ $model = $this->relation[$relation];
755
+ } else {
756
+ $model = $this->getRelationData($this->$relation());
757
+ }
758
+
759
+ if ($model instanceof Model) {
760
+ foreach ($append as $key => $attr) {
761
+ $key = is_numeric($key) ? $attr : $key;
762
+ if (isset($this->data[$key])) {
763
+ throw new Exception('bind attr has exists:' . $key);
764
+ } else {
765
+ $this->data[$key] = $model->getAttr($attr);
766
+ }
767
+ }
768
+ }
769
+ return $this;
770
+ }
771
+
772
+ /**
773
+ * 设置需要隐藏的输出属性
774
+ * @access public
775
+ * @param array $hidden 属性列表
776
+ * @param bool $override 是否覆盖
777
+ * @return $this
778
+ */
779
+ public function hidden($hidden = [], $override = false)
780
+ {
781
+ $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden);
782
+ return $this;
783
+ }
784
+
785
+ /**
786
+ * 设置需要输出的属性
787
+ * @access public
788
+ * @param array $visible
789
+ * @param bool $override 是否覆盖
790
+ * @return $this
791
+ */
792
+ public function visible($visible = [], $override = false)
793
+ {
794
+ $this->visible = $override ? $visible : array_merge($this->visible, $visible);
795
+ return $this;
796
+ }
797
+
798
+ /**
799
+ * 解析隐藏及显示属性
800
+ * @access protected
801
+ * @param array $attrs 属性
802
+ * @param array $result 结果集
803
+ * @param bool $visible
804
+ * @return array
805
+ */
806
+ protected function parseAttr($attrs, &$result, $visible = true)
807
+ {
808
+ $array = [];
809
+ foreach ($attrs as $key => $val) {
810
+ if (is_array($val)) {
811
+ if ($visible) {
812
+ $array[] = $key;
813
+ }
814
+ $result[$key] = $val;
815
+ } elseif (strpos($val, '.')) {
816
+ list($key, $name) = explode('.', $val);
817
+ if ($visible) {
818
+ $array[] = $key;
819
+ }
820
+ $result[$key][] = $name;
821
+ } else {
822
+ $array[] = $val;
823
+ }
824
+ }
825
+ return $array;
826
+ }
827
+
828
+ /**
829
+ * 转换子模型对象
830
+ * @access protected
831
+ * @param Model|ModelCollection $model
832
+ * @param $visible
833
+ * @param $hidden
834
+ * @param $key
835
+ * @return array
836
+ */
837
+ protected function subToArray($model, $visible, $hidden, $key)
838
+ {
839
+ // 关联模型对象
840
+ if (isset($visible[$key])) {
841
+ $model->visible($visible[$key]);
842
+ } elseif (isset($hidden[$key])) {
843
+ $model->hidden($hidden[$key]);
844
+ }
845
+ return $model->toArray();
846
+ }
847
+
848
+ /**
849
+ * 转换当前模型对象为数组
850
+ * @access public
851
+ * @return array
852
+ */
853
+ public function toArray()
854
+ {
855
+ $item = [];
856
+ $visible = [];
857
+ $hidden = [];
858
+
859
+ $data = array_merge($this->data, $this->relation);
860
+
861
+ // 过滤属性
862
+ if (!empty($this->visible)) {
863
+ $array = $this->parseAttr($this->visible, $visible);
864
+ $data = array_intersect_key($data, array_flip($array));
865
+ } elseif (!empty($this->hidden)) {
866
+ $array = $this->parseAttr($this->hidden, $hidden, false);
867
+ $data = array_diff_key($data, array_flip($array));
868
+ }
869
+
870
+ foreach ($data as $key => $val) {
871
+ if ($val instanceof Model || $val instanceof ModelCollection) {
872
+ // 关联模型对象
873
+ $item[$key] = $this->subToArray($val, $visible, $hidden, $key);
874
+ } elseif (is_array($val) && reset($val) instanceof Model) {
875
+ // 关联模型数据集
876
+ $arr = [];
877
+ foreach ($val as $k => $value) {
878
+ $arr[$k] = $this->subToArray($value, $visible, $hidden, $key);
879
+ }
880
+ $item[$key] = $arr;
881
+ } else {
882
+ // 模型属性
883
+ $item[$key] = $this->getAttr($key);
884
+ }
885
+ }
886
+ // 追加属性(必须定义获取器)
887
+ if (!empty($this->append)) {
888
+ foreach ($this->append as $key => $name) {
889
+ if (is_array($name)) {
890
+ // 追加关联对象属性
891
+ $relation = $this->getAttr($key);
892
+ $item[$key] = $relation->append($name)->toArray();
893
+ } elseif (strpos($name, '.')) {
894
+ list($key, $attr) = explode('.', $name);
895
+ // 追加关联对象属性
896
+ $relation = $this->getAttr($key);
897
+ $item[$key] = $relation->append([$attr])->toArray();
898
+ } else {
899
+ $relation = Loader::parseName($name, 1, false);
900
+ if (method_exists($this, $relation)) {
901
+ $modelRelation = $this->$relation();
902
+ $value = $this->getRelationData($modelRelation);
903
+
904
+ if (method_exists($modelRelation, 'getBindAttr')) {
905
+ $bindAttr = $modelRelation->getBindAttr();
906
+ if ($bindAttr) {
907
+ foreach ($bindAttr as $key => $attr) {
908
+ $key = is_numeric($key) ? $attr : $key;
909
+ if (isset($this->data[$key])) {
910
+ throw new Exception('bind attr has exists:' . $key);
911
+ } else {
912
+ $item[$key] = $value ? $value->getAttr($attr) : null;
913
+ }
914
+ }
915
+ continue;
916
+ }
917
+ }
918
+ $item[$name] = $value;
919
+ } else {
920
+ $item[$name] = $this->getAttr($name);
921
+ }
922
+ }
923
+ }
924
+ }
925
+ return !empty($item) ? $item : [];
926
+ }
927
+
928
+ /**
929
+ * 转换当前模型对象为JSON字符串
930
+ * @access public
931
+ * @param integer $options json参数
932
+ * @return string
933
+ */
934
+ public function toJson($options = JSON_UNESCAPED_UNICODE)
935
+ {
936
+ return json_encode($this->toArray(), $options);
937
+ }
938
+
939
+ /**
940
+ * 移除当前模型的关联属性
941
+ * @access public
942
+ * @return $this
943
+ */
944
+ public function removeRelation()
945
+ {
946
+ $this->relation = [];
947
+ return $this;
948
+ }
949
+
950
+ /**
951
+ * 转换当前模型数据集为数据集对象
952
+ * @access public
953
+ * @param array|\think\Collection $collection 数据集
954
+ * @return \think\Collection
955
+ */
956
+ public function toCollection($collection)
957
+ {
958
+ if ($this->resultSetType) {
959
+ if ('collection' == $this->resultSetType) {
960
+ $collection = new ModelCollection($collection);
961
+ } elseif (false !== strpos($this->resultSetType, '\\')) {
962
+ $class = $this->resultSetType;
963
+ $collection = new $class($collection);
964
+ }
965
+ }
966
+ return $collection;
967
+ }
968
+
969
+ /**
970
+ * 关联数据一起更新
971
+ * @access public
972
+ * @param mixed $relation 关联
973
+ * @return $this
974
+ */
975
+ public function together($relation)
976
+ {
977
+ if (is_string($relation)) {
978
+ $relation = explode(',', $relation);
979
+ }
980
+ $this->relationWrite = $relation;
981
+ return $this;
982
+ }
983
+
984
+ /**
985
+ * 获取模型对象的主键
986
+ * @access public
987
+ * @param string $name 模型名
988
+ * @return mixed
989
+ */
990
+ public function getPk($name = '')
991
+ {
992
+ if (!empty($name)) {
993
+ $table = $this->getQuery()->getTable($name);
994
+ return $this->getQuery()->getPk($table);
995
+ } elseif (empty($this->pk)) {
996
+ $this->pk = $this->getQuery()->getPk();
997
+ }
998
+ return $this->pk;
999
+ }
1000
+
1001
+ /**
1002
+ * 判断一个字段名是否为主键字段
1003
+ * @access public
1004
+ * @param string $key 名称
1005
+ * @return bool
1006
+ */
1007
+ protected function isPk($key)
1008
+ {
1009
+ $pk = $this->getPk();
1010
+ if (is_string($pk) && $pk == $key) {
1011
+ return true;
1012
+ } elseif (is_array($pk) && in_array($key, $pk)) {
1013
+ return true;
1014
+ }
1015
+ return false;
1016
+ }
1017
+
1018
+ /**
1019
+ * 新增数据是否使用Replace
1020
+ * @access public
1021
+ * @param bool $replace
1022
+ * @return $this
1023
+ */
1024
+ public function replace($replace = true)
1025
+ {
1026
+ $this->replace = $replace;
1027
+ return $this;
1028
+ }
1029
+
1030
+ /**
1031
+ * 保存当前数据对象
1032
+ * @access public
1033
+ * @param array $data 数据
1034
+ * @param array $where 更新条件
1035
+ * @param string $sequence 自增序列名
1036
+ * @return integer|false
1037
+ */
1038
+ public function save($data = [], $where = [], $sequence = null)
1039
+ {
1040
+ if (is_string($data)) {
1041
+ $sequence = $data;
1042
+ $data = [];
1043
+ }
1044
+
1045
+ // 数据自动验证
1046
+ if (!empty($data)) {
1047
+ if (!$this->validateData($data)) {
1048
+ return false;
1049
+ }
1050
+
1051
+ // 数据对象赋值
1052
+ foreach ($data as $key => $value) {
1053
+ $this->setAttr($key, $value, $data);
1054
+ }
1055
+ }
1056
+
1057
+ if (!empty($where)) {
1058
+ $this->isUpdate = true;
1059
+ $this->updateWhere = $where;
1060
+ }
1061
+
1062
+ // 自动关联写入
1063
+ if (!empty($this->relationWrite)) {
1064
+ $relation = [];
1065
+ foreach ($this->relationWrite as $key => $name) {
1066
+ if (is_array($name)) {
1067
+ if (key($name) === 0) {
1068
+ $relation[$key] = [];
1069
+ foreach ($name as $val) {
1070
+ if (isset($this->data[$val])) {
1071
+ $relation[$key][$val] = $this->data[$val];
1072
+ unset($this->data[$val]);
1073
+ }
1074
+ }
1075
+ } else {
1076
+ $relation[$key] = $name;
1077
+ }
1078
+ } elseif (isset($this->relation[$name])) {
1079
+ $relation[$name] = $this->relation[$name];
1080
+ } elseif (isset($this->data[$name])) {
1081
+ $relation[$name] = $this->data[$name];
1082
+ unset($this->data[$name]);
1083
+ }
1084
+ }
1085
+ }
1086
+
1087
+ // 数据自动完成
1088
+ $this->autoCompleteData($this->auto);
1089
+
1090
+ // 事件回调
1091
+ if (false === $this->trigger('before_write', $this)) {
1092
+ return false;
1093
+ }
1094
+ $pk = $this->getPk();
1095
+ if ($this->isUpdate) {
1096
+ // 自动更新
1097
+ $this->autoCompleteData($this->update);
1098
+
1099
+ // 事件回调
1100
+ if (false === $this->trigger('before_update', $this)) {
1101
+ return false;
1102
+ }
1103
+
1104
+ // 获取有更新的数据
1105
+ $data = $this->getChangedData();
1106
+
1107
+ if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) {
1108
+ // 关联更新
1109
+ if (isset($relation)) {
1110
+ $this->autoRelationUpdate($relation);
1111
+ }
1112
+ return 0;
1113
+ } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
1114
+ // 自动写入更新时间
1115
+ $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
1116
+ $this->data[$this->updateTime] = $data[$this->updateTime];
1117
+ }
1118
+
1119
+ if (empty($where) && !empty($this->updateWhere)) {
1120
+ $where = $this->updateWhere;
1121
+ }
1122
+
1123
+ // 保留主键数据
1124
+ foreach ($this->data as $key => $val) {
1125
+ if ($this->isPk($key)) {
1126
+ $data[$key] = $val;
1127
+ }
1128
+ }
1129
+
1130
+ $array = [];
1131
+
1132
+ foreach ((array) $pk as $key) {
1133
+ if (isset($data[$key])) {
1134
+ $array[$key] = $data[$key];
1135
+ unset($data[$key]);
1136
+ }
1137
+ }
1138
+
1139
+ if (!empty($array)) {
1140
+ $where = $array;
1141
+ }
1142
+
1143
+ // 检测字段
1144
+ $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update));
1145
+
1146
+ // 模型更新
1147
+ if (!empty($allowFields)) {
1148
+ $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data);
1149
+ } else {
1150
+ $result = $this->getQuery()->where($where)->update($data);
1151
+ }
1152
+
1153
+ // 关联更新
1154
+ if (isset($relation)) {
1155
+ $this->autoRelationUpdate($relation);
1156
+ }
1157
+
1158
+ // 更新回调
1159
+ $this->trigger('after_update', $this);
1160
+
1161
+ } else {
1162
+ // 自动写入
1163
+ $this->autoCompleteData($this->insert);
1164
+
1165
+ // 自动写入创建时间和更新时间
1166
+ if ($this->autoWriteTimestamp) {
1167
+ if ($this->createTime && !isset($this->data[$this->createTime])) {
1168
+ $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime);
1169
+ }
1170
+ if ($this->updateTime && !isset($this->data[$this->updateTime])) {
1171
+ $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
1172
+ }
1173
+ }
1174
+
1175
+ if (false === $this->trigger('before_insert', $this)) {
1176
+ return false;
1177
+ }
1178
+
1179
+ // 检测字段
1180
+ $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert));
1181
+ if (!empty($allowFields)) {
1182
+ $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence);
1183
+ } else {
1184
+ $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence);
1185
+ }
1186
+
1187
+ // 获取自动增长主键
1188
+ if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) {
1189
+ foreach ((array) $pk as $key) {
1190
+ if (!isset($this->data[$key]) || '' == $this->data[$key]) {
1191
+ $this->data[$key] = $insertId;
1192
+ }
1193
+ }
1194
+ }
1195
+
1196
+ // 关联写入
1197
+ if (isset($relation)) {
1198
+ foreach ($relation as $name => $val) {
1199
+ $method = Loader::parseName($name, 1, false);
1200
+ $this->$method()->save($val);
1201
+ }
1202
+ }
1203
+
1204
+ // 标记为更新
1205
+ $this->isUpdate = true;
1206
+
1207
+ // 新增回调
1208
+ $this->trigger('after_insert', $this);
1209
+ }
1210
+ // 写入回调
1211
+ $this->trigger('after_write', $this);
1212
+
1213
+ // 重新记录原始数据
1214
+ $this->origin = $this->data;
1215
+
1216
+ return $result;
1217
+ }
1218
+
1219
+ protected function checkAllowField($auto = [])
1220
+ {
1221
+ if (true === $this->field) {
1222
+ $this->field = $this->getQuery()->getTableInfo('', 'fields');
1223
+ $field = $this->field;
1224
+ } elseif (!empty($this->field)) {
1225
+ $field = array_merge($this->field, $auto);
1226
+ if ($this->autoWriteTimestamp) {
1227
+ array_push($field, $this->createTime, $this->updateTime);
1228
+ }
1229
+ } elseif (!empty($this->except)) {
1230
+ $fields = $this->getQuery()->getTableInfo('', 'fields');
1231
+ $field = array_diff($fields, (array) $this->except);
1232
+ $this->field = $field;
1233
+ } else {
1234
+ $field = [];
1235
+ }
1236
+
1237
+ if ($this->disuse) {
1238
+ // 废弃字段
1239
+ $field = array_diff($field, (array) $this->disuse);
1240
+ }
1241
+ return $field;
1242
+ }
1243
+
1244
+ protected function autoRelationUpdate($relation)
1245
+ {
1246
+ foreach ($relation as $name => $val) {
1247
+ if ($val instanceof Model) {
1248
+ $val->save();
1249
+ } else {
1250
+ unset($this->data[$name]);
1251
+ $model = $this->getAttr($name);
1252
+ if ($model instanceof Model) {
1253
+ $model->save($val);
1254
+ }
1255
+ }
1256
+ }
1257
+ }
1258
+
1259
+ /**
1260
+ * 获取变化的数据 并排除只读数据
1261
+ * @access public
1262
+ * @return array
1263
+ */
1264
+ public function getChangedData()
1265
+ {
1266
+ if ($this->force) {
1267
+ $data = $this->data;
1268
+ } else {
1269
+ $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
1270
+ if ((empty($a) || empty($b)) && $a !== $b) {
1271
+ return 1;
1272
+ }
1273
+ return is_object($a) || $a != $b ? 1 : 0;
1274
+ });
1275
+ }
1276
+
1277
+ if (!empty($this->readonly)) {
1278
+ // 只读字段不允许更新
1279
+ foreach ($this->readonly as $key => $field) {
1280
+ if (isset($data[$field])) {
1281
+ unset($data[$field]);
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ return $data;
1287
+ }
1288
+
1289
+ /**
1290
+ * 字段值(延迟)增长
1291
+ * @access public
1292
+ * @param string $field 字段名
1293
+ * @param integer $step 增长值
1294
+ * @param integer $lazyTime 延时时间(s)
1295
+ * @return integer|true
1296
+ * @throws Exception
1297
+ */
1298
+ public function setInc($field, $step = 1, $lazyTime = 0)
1299
+ {
1300
+ // 更新条件
1301
+ $where = $this->getWhere();
1302
+
1303
+ $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime);
1304
+ if (true !== $result) {
1305
+ $this->data[$field] += $step;
1306
+ }
1307
+
1308
+ return $result;
1309
+ }
1310
+
1311
+ /**
1312
+ * 字段值(延迟)增长
1313
+ * @access public
1314
+ * @param string $field 字段名
1315
+ * @param integer $step 增长值
1316
+ * @param integer $lazyTime 延时时间(s)
1317
+ * @return integer|true
1318
+ * @throws Exception
1319
+ */
1320
+ public function setDec($field, $step = 1, $lazyTime = 0)
1321
+ {
1322
+ // 更新条件
1323
+ $where = $this->getWhere();
1324
+ $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime);
1325
+ if (true !== $result) {
1326
+ $this->data[$field] -= $step;
1327
+ }
1328
+
1329
+ return $result;
1330
+ }
1331
+
1332
+ /**
1333
+ * 获取更新条件
1334
+ * @access protected
1335
+ * @return mixed
1336
+ */
1337
+ protected function getWhere()
1338
+ {
1339
+ // 删除条件
1340
+ $pk = $this->getPk();
1341
+
1342
+ if (is_string($pk) && isset($this->data[$pk])) {
1343
+ $where = [$pk => $this->data[$pk]];
1344
+ } elseif (!empty($this->updateWhere)) {
1345
+ $where = $this->updateWhere;
1346
+ } else {
1347
+ $where = null;
1348
+ }
1349
+ return $where;
1350
+ }
1351
+
1352
+ /**
1353
+ * 保存多个数据到当前数据对象
1354
+ * @access public
1355
+ * @param array $dataSet 数据
1356
+ * @param boolean $replace 是否自动识别更新和写入
1357
+ * @return array|false
1358
+ * @throws \Exception
1359
+ */
1360
+ public function saveAll($dataSet, $replace = true)
1361
+ {
1362
+ if ($this->validate) {
1363
+ // 数据批量验证
1364
+ $validate = $this->validate;
1365
+ foreach ($dataSet as $data) {
1366
+ if (!$this->validateData($data, $validate)) {
1367
+ return false;
1368
+ }
1369
+ }
1370
+ }
1371
+
1372
+ $result = [];
1373
+ $db = $this->getQuery();
1374
+ $db->startTrans();
1375
+ try {
1376
+ $pk = $this->getPk();
1377
+ if (is_string($pk) && $replace) {
1378
+ $auto = true;
1379
+ }
1380
+ foreach ($dataSet as $key => $data) {
1381
+ if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) {
1382
+ $result[$key] = self::update($data, [], $this->field);
1383
+ } else {
1384
+ $result[$key] = self::create($data, $this->field);
1385
+ }
1386
+ }
1387
+ $db->commit();
1388
+ return $this->toCollection($result);
1389
+ } catch (\Exception $e) {
1390
+ $db->rollback();
1391
+ throw $e;
1392
+ }
1393
+ }
1394
+
1395
+ /**
1396
+ * 设置允许写入的字段
1397
+ * @access public
1398
+ * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段
1399
+ * @return $this
1400
+ */
1401
+ public function allowField($field)
1402
+ {
1403
+ if (is_string($field)) {
1404
+ $field = explode(',', $field);
1405
+ }
1406
+ $this->field = $field;
1407
+ return $this;
1408
+ }
1409
+
1410
+ /**
1411
+ * 设置排除写入的字段
1412
+ * @access public
1413
+ * @param string|array $field 排除允许写入的字段
1414
+ * @return $this
1415
+ */
1416
+ public function except($field)
1417
+ {
1418
+ if (is_string($field)) {
1419
+ $field = explode(',', $field);
1420
+ }
1421
+ $this->except = $field;
1422
+ return $this;
1423
+ }
1424
+
1425
+ /**
1426
+ * 设置只读字段
1427
+ * @access public
1428
+ * @param mixed $field 只读字段
1429
+ * @return $this
1430
+ */
1431
+ public function readonly($field)
1432
+ {
1433
+ if (is_string($field)) {
1434
+ $field = explode(',', $field);
1435
+ }
1436
+ $this->readonly = $field;
1437
+ return $this;
1438
+ }
1439
+
1440
+ /**
1441
+ * 是否为更新数据
1442
+ * @access public
1443
+ * @param bool $update
1444
+ * @param mixed $where
1445
+ * @return $this
1446
+ */
1447
+ public function isUpdate($update = true, $where = null)
1448
+ {
1449
+ $this->isUpdate = $update;
1450
+ if (!empty($where)) {
1451
+ $this->updateWhere = $where;
1452
+ }
1453
+ return $this;
1454
+ }
1455
+
1456
+ /**
1457
+ * 数据自动完成
1458
+ * @access public
1459
+ * @param array $auto 要自动更新的字段列表
1460
+ * @return void
1461
+ */
1462
+ protected function autoCompleteData($auto = [])
1463
+ {
1464
+ foreach ($auto as $field => $value) {
1465
+ if (is_integer($field)) {
1466
+ $field = $value;
1467
+ $value = null;
1468
+ }
1469
+
1470
+ if (!isset($this->data[$field])) {
1471
+ $default = null;
1472
+ } else {
1473
+ $default = $this->data[$field];
1474
+ }
1475
+
1476
+ $this->setAttr($field, !is_null($value) ? $value : $default);
1477
+ }
1478
+ }
1479
+
1480
+ /**
1481
+ * 删除当前的记录
1482
+ * @access public
1483
+ * @return integer
1484
+ */
1485
+ public function delete()
1486
+ {
1487
+ if (false === $this->trigger('before_delete', $this)) {
1488
+ return false;
1489
+ }
1490
+
1491
+ // 删除条件
1492
+ $where = $this->getWhere();
1493
+
1494
+ // 删除当前模型数据
1495
+ $result = $this->getQuery()->where($where)->delete();
1496
+
1497
+ // 关联删除
1498
+ if (!empty($this->relationWrite)) {
1499
+ foreach ($this->relationWrite as $key => $name) {
1500
+ $name = is_numeric($key) ? $name : $key;
1501
+ $model = $this->getAttr($name);
1502
+ if ($model instanceof Model) {
1503
+ $model->delete();
1504
+ }
1505
+ }
1506
+ }
1507
+
1508
+ $this->trigger('after_delete', $this);
1509
+ // 清空原始数据
1510
+ $this->origin = [];
1511
+
1512
+ return $result;
1513
+ }
1514
+
1515
+ /**
1516
+ * 设置自动完成的字段( 规则通过修改器定义)
1517
+ * @access public
1518
+ * @param array $fields 需要自动完成的字段
1519
+ * @return $this
1520
+ */
1521
+ public function auto($fields)
1522
+ {
1523
+ $this->auto = $fields;
1524
+ return $this;
1525
+ }
1526
+
1527
+ /**
1528
+ * 设置字段验证
1529
+ * @access public
1530
+ * @param array|string|bool $rule 验证规则 true表示自动读取验证器类
1531
+ * @param array $msg 提示信息
1532
+ * @param bool $batch 批量验证
1533
+ * @return $this
1534
+ */
1535
+ public function validate($rule = true, $msg = [], $batch = false)
1536
+ {
1537
+ if (is_array($rule)) {
1538
+ $this->validate = [
1539
+ 'rule' => $rule,
1540
+ 'msg' => $msg,
1541
+ ];
1542
+ } else {
1543
+ $this->validate = true === $rule ? $this->name : $rule;
1544
+ }
1545
+ $this->batchValidate = $batch;
1546
+ return $this;
1547
+ }
1548
+
1549
+ /**
1550
+ * 设置验证失败后是否抛出异常
1551
+ * @access public
1552
+ * @param bool $fail 是否抛出异常
1553
+ * @return $this
1554
+ */
1555
+ public function validateFailException($fail = true)
1556
+ {
1557
+ $this->failException = $fail;
1558
+ return $this;
1559
+ }
1560
+
1561
+ /**
1562
+ * 自动验证数据
1563
+ * @access protected
1564
+ * @param array $data 验证数据
1565
+ * @param mixed $rule 验证规则
1566
+ * @param bool $batch 批量验证
1567
+ * @return bool
1568
+ */
1569
+ protected function validateData($data, $rule = null, $batch = null)
1570
+ {
1571
+ $info = is_null($rule) ? $this->validate : $rule;
1572
+
1573
+ if (!empty($info)) {
1574
+ if (is_array($info)) {
1575
+ $validate = Loader::validate();
1576
+ $validate->rule($info['rule']);
1577
+ $validate->message($info['msg']);
1578
+ } else {
1579
+ $name = is_string($info) ? $info : $this->name;
1580
+ if (strpos($name, '.')) {
1581
+ list($name, $scene) = explode('.', $name);
1582
+ }
1583
+ $validate = Loader::validate($name);
1584
+ if (!empty($scene)) {
1585
+ $validate->scene($scene);
1586
+ }
1587
+ }
1588
+ $batch = is_null($batch) ? $this->batchValidate : $batch;
1589
+
1590
+ if (!$validate->batch($batch)->check($data)) {
1591
+ $this->error = $validate->getError();
1592
+ if ($this->failException) {
1593
+ throw new ValidateException($this->error);
1594
+ } else {
1595
+ return false;
1596
+ }
1597
+ }
1598
+ $this->validate = null;
1599
+ }
1600
+ return true;
1601
+ }
1602
+
1603
+ /**
1604
+ * 返回模型的错误信息
1605
+ * @access public
1606
+ * @return string|array
1607
+ */
1608
+ public function getError()
1609
+ {
1610
+ return $this->error;
1611
+ }
1612
+
1613
+ /**
1614
+ * 注册回调方法
1615
+ * @access public
1616
+ * @param string $event 事件名
1617
+ * @param callable $callback 回调方法
1618
+ * @param bool $override 是否覆盖
1619
+ * @return void
1620
+ */
1621
+ public static function event($event, $callback, $override = false)
1622
+ {
1623
+ $class = get_called_class();
1624
+ if ($override) {
1625
+ self::$event[$class][$event] = [];
1626
+ }
1627
+ self::$event[$class][$event][] = $callback;
1628
+ }
1629
+
1630
+ /**
1631
+ * 触发事件
1632
+ * @access protected
1633
+ * @param string $event 事件名
1634
+ * @param mixed $params 传入参数(引用)
1635
+ * @return bool
1636
+ */
1637
+ protected function trigger($event, &$params)
1638
+ {
1639
+ if (isset(self::$event[$this->class][$event])) {
1640
+ foreach (self::$event[$this->class][$event] as $callback) {
1641
+ if (is_callable($callback)) {
1642
+ $result = call_user_func_array($callback, [ & $params]);
1643
+ if (false === $result) {
1644
+ return false;
1645
+ }
1646
+ }
1647
+ }
1648
+ }
1649
+ return true;
1650
+ }
1651
+
1652
+ /**
1653
+ * 写入数据
1654
+ * @access public
1655
+ * @param array $data 数据数组
1656
+ * @param array|true $field 允许字段
1657
+ * @return $this
1658
+ */
1659
+ public static function create($data = [], $field = null)
1660
+ {
1661
+ $model = new static();
1662
+ if (!empty($field)) {
1663
+ $model->allowField($field);
1664
+ }
1665
+ $model->isUpdate(false)->save($data, []);
1666
+ return $model;
1667
+ }
1668
+
1669
+ /**
1670
+ * 更新数据
1671
+ * @access public
1672
+ * @param array $data 数据数组
1673
+ * @param array $where 更新条件
1674
+ * @param array|true $field 允许字段
1675
+ * @return $this
1676
+ */
1677
+ public static function update($data = [], $where = [], $field = null)
1678
+ {
1679
+ $model = new static();
1680
+ if (!empty($field)) {
1681
+ $model->allowField($field);
1682
+ }
1683
+ $result = $model->isUpdate(true)->save($data, $where);
1684
+ return $model;
1685
+ }
1686
+
1687
+ /**
1688
+ * 查找单条记录
1689
+ * @access public
1690
+ * @param mixed $data 主键值或者查询条件(闭包)
1691
+ * @param array|string $with 关联预查询
1692
+ * @param bool $cache 是否缓存
1693
+ * @return static|null
1694
+ * @throws exception\DbException
1695
+ */
1696
+ public static function get($data, $with = [], $cache = false)
1697
+ {
1698
+ if (is_null($data)) {
1699
+ return;
1700
+ }
1701
+
1702
+ if (true === $with || is_int($with)) {
1703
+ $cache = $with;
1704
+ $with = [];
1705
+ }
1706
+ $query = static::parseQuery($data, $with, $cache);
1707
+ return $query->find($data);
1708
+ }
1709
+
1710
+ /**
1711
+ * 查找所有记录
1712
+ * @access public
1713
+ * @param mixed $data 主键列表或者查询条件(闭包)
1714
+ * @param array|string $with 关联预查询
1715
+ * @param bool $cache 是否缓存
1716
+ * @return static[]|false
1717
+ * @throws exception\DbException
1718
+ */
1719
+ public static function all($data = null, $with = [], $cache = false)
1720
+ {
1721
+ if (true === $with || is_int($with)) {
1722
+ $cache = $with;
1723
+ $with = [];
1724
+ }
1725
+ $query = static::parseQuery($data, $with, $cache);
1726
+ return $query->select($data);
1727
+ }
1728
+
1729
+ /**
1730
+ * 分析查询表达式
1731
+ * @access public
1732
+ * @param mixed $data 主键列表或者查询条件(闭包)
1733
+ * @param string $with 关联预查询
1734
+ * @param bool $cache 是否缓存
1735
+ * @return Query
1736
+ */
1737
+ protected static function parseQuery(&$data, $with, $cache)
1738
+ {
1739
+ $result = self::with($with)->cache($cache);
1740
+ if (is_array($data) && key($data) !== 0) {
1741
+ $result = $result->where($data);
1742
+ $data = null;
1743
+ } elseif ($data instanceof \Closure) {
1744
+ call_user_func_array($data, [ & $result]);
1745
+ $data = null;
1746
+ } elseif ($data instanceof Query) {
1747
+ $result = $data->with($with)->cache($cache);
1748
+ $data = null;
1749
+ }
1750
+ return $result;
1751
+ }
1752
+
1753
+ /**
1754
+ * 删除记录
1755
+ * @access public
1756
+ * @param mixed $data 主键列表 支持闭包查询条件
1757
+ * @return integer 成功删除的记录数
1758
+ */
1759
+ public static function destroy($data)
1760
+ {
1761
+ $model = new static();
1762
+ $query = $model->db();
1763
+ if (empty($data) && 0 !== $data) {
1764
+ return 0;
1765
+ } elseif (is_array($data) && key($data) !== 0) {
1766
+ $query->where($data);
1767
+ $data = null;
1768
+ } elseif ($data instanceof \Closure) {
1769
+ call_user_func_array($data, [ & $query]);
1770
+ $data = null;
1771
+ }
1772
+ $resultSet = $query->select($data);
1773
+ $count = 0;
1774
+ if ($resultSet) {
1775
+ foreach ($resultSet as $data) {
1776
+ $result = $data->delete();
1777
+ $count += $result;
1778
+ }
1779
+ }
1780
+ return $count;
1781
+ }
1782
+
1783
+ /**
1784
+ * 命名范围
1785
+ * @access public
1786
+ * @param string|array|\Closure $name 命名范围名称 逗号分隔
1787
+ * @internal mixed ...$params 参数调用
1788
+ * @return Query
1789
+ */
1790
+ public static function scope($name)
1791
+ {
1792
+ $model = new static();
1793
+ $query = $model->db();
1794
+ $params = func_get_args();
1795
+ array_shift($params);
1796
+ array_unshift($params, $query);
1797
+ if ($name instanceof \Closure) {
1798
+ call_user_func_array($name, $params);
1799
+ } elseif (is_string($name)) {
1800
+ $name = explode(',', $name);
1801
+ }
1802
+ if (is_array($name)) {
1803
+ foreach ($name as $scope) {
1804
+ $method = 'scope' . trim($scope);
1805
+ if (method_exists($model, $method)) {
1806
+ call_user_func_array([$model, $method], $params);
1807
+ }
1808
+ }
1809
+ }
1810
+ return $query;
1811
+ }
1812
+
1813
+ /**
1814
+ * 设置是否使用全局查询范围
1815
+ * @param bool $use 是否启用全局查询范围
1816
+ * @access public
1817
+ * @return Query
1818
+ */
1819
+ public static function useGlobalScope($use)
1820
+ {
1821
+ $model = new static();
1822
+ return $model->db($use);
1823
+ }
1824
+
1825
+ /**
1826
+ * 根据关联条件查询当前模型
1827
+ * @access public
1828
+ * @param string $relation 关联方法名
1829
+ * @param mixed $operator 比���操作符
1830
+ * @param integer $count 个数
1831
+ * @param string $id 关联表的统计字段
1832
+ * @return Relation|Query
1833
+ */
1834
+ public static function has($relation, $operator = '>=', $count = 1, $id = '*')
1835
+ {
1836
+ $relation = (new static())->$relation();
1837
+ if (is_array($operator) || $operator instanceof \Closure) {
1838
+ return $relation->hasWhere($operator);
1839
+ }
1840
+ return $relation->has($operator, $count, $id);
1841
+ }
1842
+
1843
+ /**
1844
+ * 根据关联条件查询当前模型
1845
+ * @access public
1846
+ * @param string $relation 关联方法名
1847
+ * @param mixed $where 查询条件(数组或者闭包)
1848
+ * @param mixed $fields 字段
1849
+ * @return Relation|Query
1850
+ */
1851
+ public static function hasWhere($relation, $where = [], $fields = null)
1852
+ {
1853
+ return (new static())->$relation()->hasWhere($where, $fields);
1854
+ }
1855
+
1856
+ /**
1857
+ * 解析模型的完整命名空间
1858
+ * @access public
1859
+ * @param string $model 模型名(或者完整类名)
1860
+ * @return string
1861
+ */
1862
+ protected function parseModel($model)
1863
+ {
1864
+ if (false === strpos($model, '\\')) {
1865
+ $path = explode('\\', get_called_class());
1866
+ array_pop($path);
1867
+ array_push($path, Loader::parseName($model, 1));
1868
+ $model = implode('\\', $path);
1869
+ }
1870
+ return $model;
1871
+ }
1872
+
1873
+ /**
1874
+ * 查询当前模型的关联数据
1875
+ * @access public
1876
+ * @param string|array $relations 关联名
1877
+ * @return $this
1878
+ */
1879
+ public function relationQuery($relations)
1880
+ {
1881
+ if (is_string($relations)) {
1882
+ $relations = explode(',', $relations);
1883
+ }
1884
+
1885
+ foreach ($relations as $key => $relation) {
1886
+ $subRelation = '';
1887
+ $closure = null;
1888
+ if ($relation instanceof \Closure) {
1889
+ // 支持闭包查询过滤关联条件
1890
+ $closure = $relation;
1891
+ $relation = $key;
1892
+ }
1893
+ if (is_array($relation)) {
1894
+ $subRelation = $relation;
1895
+ $relation = $key;
1896
+ } elseif (strpos($relation, '.')) {
1897
+ list($relation, $subRelation) = explode('.', $relation, 2);
1898
+ }
1899
+ $method = Loader::parseName($relation, 1, false);
1900
+ $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure);
1901
+ }
1902
+ return $this;
1903
+ }
1904
+
1905
+ /**
1906
+ * 预载入关联查询 返回数据集
1907
+ * @access public
1908
+ * @param array $resultSet 数据集
1909
+ * @param string $relation 关联名
1910
+ * @return array
1911
+ */
1912
+ public function eagerlyResultSet(&$resultSet, $relation)
1913
+ {
1914
+ $relations = is_string($relation) ? explode(',', $relation) : $relation;
1915
+ foreach ($relations as $key => $relation) {
1916
+ $subRelation = '';
1917
+ $closure = false;
1918
+ if ($relation instanceof \Closure) {
1919
+ $closure = $relation;
1920
+ $relation = $key;
1921
+ }
1922
+ if (is_array($relation)) {
1923
+ $subRelation = $relation;
1924
+ $relation = $key;
1925
+ } elseif (strpos($relation, '.')) {
1926
+ list($relation, $subRelation) = explode('.', $relation, 2);
1927
+ }
1928
+ $relation = Loader::parseName($relation, 1, false);
1929
+ $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure);
1930
+ }
1931
+ }
1932
+
1933
+ /**
1934
+ * 预载入关联查询 返回模型对象
1935
+ * @access public
1936
+ * @param Model $result 数据对象
1937
+ * @param string $relation 关联名
1938
+ * @return Model
1939
+ */
1940
+ public function eagerlyResult(&$result, $relation)
1941
+ {
1942
+ $relations = is_string($relation) ? explode(',', $relation) : $relation;
1943
+
1944
+ foreach ($relations as $key => $relation) {
1945
+ $subRelation = '';
1946
+ $closure = false;
1947
+ if ($relation instanceof \Closure) {
1948
+ $closure = $relation;
1949
+ $relation = $key;
1950
+ }
1951
+ if (is_array($relation)) {
1952
+ $subRelation = $relation;
1953
+ $relation = $key;
1954
+ } elseif (strpos($relation, '.')) {
1955
+ list($relation, $subRelation) = explode('.', $relation, 2);
1956
+ }
1957
+ $relation = Loader::parseName($relation, 1, false);
1958
+ $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure);
1959
+ }
1960
+ }
1961
+
1962
+ /**
1963
+ * 关联统计
1964
+ * @access public
1965
+ * @param Model $result 数据对象
1966
+ * @param string|array $relation 关联名
1967
+ * @return void
1968
+ */
1969
+ public function relationCount(&$result, $relation)
1970
+ {
1971
+ $relations = is_string($relation) ? explode(',', $relation) : $relation;
1972
+
1973
+ foreach ($relations as $key => $relation) {
1974
+ $closure = false;
1975
+ if ($relation instanceof \Closure) {
1976
+ $closure = $relation;
1977
+ $relation = $key;
1978
+ } elseif (is_string($key)) {
1979
+ $name = $relation;
1980
+ $relation = $key;
1981
+ }
1982
+ $relation = Loader::parseName($relation, 1, false);
1983
+ $count = $this->$relation()->relationCount($result, $closure);
1984
+ if (!isset($name)) {
1985
+ $name = Loader::parseName($relation) . '_count';
1986
+ }
1987
+ $result->setAttr($name, $count);
1988
+ }
1989
+ }
1990
+
1991
+ /**
1992
+ * 获取模型的默认外键名
1993
+ * @access public
1994
+ * @param string $name 模型名
1995
+ * @return string
1996
+ */
1997
+ protected function getForeignKey($name)
1998
+ {
1999
+ if (strpos($name, '\\')) {
2000
+ $name = basename(str_replace('\\', '/', $name));
2001
+ }
2002
+ return Loader::parseName($name) . '_id';
2003
+ }
2004
+
2005
+ /**
2006
+ * HAS ONE 关联定义
2007
+ * @access public
2008
+ * @param string $model 模型名
2009
+ * @param string $foreignKey 关联外键
2010
+ * @param string $localKey 当前模型主键
2011
+ * @param array $alias 别名定义(已经废弃)
2012
+ * @param string $joinType JOIN类型
2013
+ * @return HasOne
2014
+ */
2015
+ public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER')
2016
+ {
2017
+ // 记录当前关联信息
2018
+ $model = $this->parseModel($model);
2019
+ $localKey = $localKey ?: $this->getPk();
2020
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
2021
+ return new HasOne($this, $model, $foreignKey, $localKey, $joinType);
2022
+ }
2023
+
2024
+ /**
2025
+ * BELONGS TO 关联定义
2026
+ * @access public
2027
+ * @param string $model 模型名
2028
+ * @param string $foreignKey 关联外键
2029
+ * @param string $localKey 关联主键
2030
+ * @param array $alias 别名定义(已经废弃)
2031
+ * @param string $joinType JOIN类型
2032
+ * @return BelongsTo
2033
+ */
2034
+ public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER')
2035
+ {
2036
+ // 记录当前关联信息
2037
+ $model = $this->parseModel($model);
2038
+ $foreignKey = $foreignKey ?: $this->getForeignKey($model);
2039
+ $localKey = $localKey ?: (new $model)->getPk();
2040
+ $trace = debug_backtrace(false, 2);
2041
+ $relation = Loader::parseName($trace[1]['function']);
2042
+ return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation);
2043
+ }
2044
+
2045
+ /**
2046
+ * HAS MANY 关联定义
2047
+ * @access public
2048
+ * @param string $model 模型名
2049
+ * @param string $foreignKey 关联外键
2050
+ * @param string $localKey 当前模型主键
2051
+ * @return HasMany
2052
+ */
2053
+ public function hasMany($model, $foreignKey = '', $localKey = '')
2054
+ {
2055
+ // 记录当前关联信息
2056
+ $model = $this->parseModel($model);
2057
+ $localKey = $localKey ?: $this->getPk();
2058
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
2059
+ return new HasMany($this, $model, $foreignKey, $localKey);
2060
+ }
2061
+
2062
+ /**
2063
+ * HAS MANY 远程关联定义
2064
+ * @access public
2065
+ * @param string $model 模型名
2066
+ * @param string $through 中间模型名
2067
+ * @param string $foreignKey 关联外键
2068
+ * @param string $throughKey 关联外键
2069
+ * @param string $localKey 当前模型主键
2070
+ * @return HasManyThrough
2071
+ */
2072
+ public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '')
2073
+ {
2074
+ // 记录当前关联信息
2075
+ $model = $this->parseModel($model);
2076
+ $through = $this->parseModel($through);
2077
+ $localKey = $localKey ?: $this->getPk();
2078
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
2079
+ $throughKey = $throughKey ?: $this->getForeignKey($through);
2080
+ return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey);
2081
+ }
2082
+
2083
+ /**
2084
+ * BELONGS TO MANY 关联定义
2085
+ * @access public
2086
+ * @param string $model 模型名
2087
+ * @param string $table 中间表名
2088
+ * @param string $foreignKey 关联外键
2089
+ * @param string $localKey 当前模型关联键
2090
+ * @return BelongsToMany
2091
+ */
2092
+ public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '')
2093
+ {
2094
+ // 记录当前关联信息
2095
+ $model = $this->parseModel($model);
2096
+ $name = Loader::parseName(basename(str_replace('\\', '/', $model)));
2097
+ $table = $table ?: Loader::parseName($this->name) . '_' . $name;
2098
+ $foreignKey = $foreignKey ?: $name . '_id';
2099
+ $localKey = $localKey ?: $this->getForeignKey($this->name);
2100
+ return new BelongsToMany($this, $model, $table, $foreignKey, $localKey);
2101
+ }
2102
+
2103
+ /**
2104
+ * MORPH MANY 关联定义
2105
+ * @access public
2106
+ * @param string $model 模型名
2107
+ * @param string|array $morph 多态字段信息
2108
+ * @param string $type 多态类型
2109
+ * @return MorphMany
2110
+ */
2111
+ public function morphMany($model, $morph = null, $type = '')
2112
+ {
2113
+ // 记录当前关联信息
2114
+ $model = $this->parseModel($model);
2115
+ if (is_null($morph)) {
2116
+ $trace = debug_backtrace(false, 2);
2117
+ $morph = Loader::parseName($trace[1]['function']);
2118
+ }
2119
+ $type = $type ?: get_class($this);
2120
+ if (is_array($morph)) {
2121
+ list($morphType, $foreignKey) = $morph;
2122
+ } else {
2123
+ $morphType = $morph . '_type';
2124
+ $foreignKey = $morph . '_id';
2125
+ }
2126
+ return new MorphMany($this, $model, $foreignKey, $morphType, $type);
2127
+ }
2128
+
2129
+ /**
2130
+ * MORPH One 关联定义
2131
+ * @access public
2132
+ * @param string $model 模型名
2133
+ * @param string|array $morph 多态字段信息
2134
+ * @param string $type 多态类型
2135
+ * @return MorphOne
2136
+ */
2137
+ public function morphOne($model, $morph = null, $type = '')
2138
+ {
2139
+ // 记录当前关联信息
2140
+ $model = $this->parseModel($model);
2141
+ if (is_null($morph)) {
2142
+ $trace = debug_backtrace(false, 2);
2143
+ $morph = Loader::parseName($trace[1]['function']);
2144
+ }
2145
+ $type = $type ?: get_class($this);
2146
+ if (is_array($morph)) {
2147
+ list($morphType, $foreignKey) = $morph;
2148
+ } else {
2149
+ $morphType = $morph . '_type';
2150
+ $foreignKey = $morph . '_id';
2151
+ }
2152
+ return new MorphOne($this, $model, $foreignKey, $morphType, $type);
2153
+ }
2154
+
2155
+ /**
2156
+ * MORPH TO 关联定义
2157
+ * @access public
2158
+ * @param string|array $morph 多态字段信息
2159
+ * @param array $alias 多态别名定义
2160
+ * @return MorphTo
2161
+ */
2162
+ public function morphTo($morph = null, $alias = [])
2163
+ {
2164
+ $trace = debug_backtrace(false, 2);
2165
+ $relation = Loader::parseName($trace[1]['function']);
2166
+
2167
+ if (is_null($morph)) {
2168
+ $morph = $relation;
2169
+ }
2170
+ // 记录当前关联信息
2171
+ if (is_array($morph)) {
2172
+ list($morphType, $foreignKey) = $morph;
2173
+ } else {
2174
+ $morphType = $morph . '_type';
2175
+ $foreignKey = $morph . '_id';
2176
+ }
2177
+ return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
2178
+ }
2179
+
2180
+ public function __call($method, $args)
2181
+ {
2182
+ $query = $this->db(true, false);
2183
+ if (method_exists($this, 'scope' . $method)) {
2184
+ // 动态调用命名范围
2185
+ $method = 'scope' . $method;
2186
+ array_unshift($args, $query);
2187
+ call_user_func_array([$this, $method], $args);
2188
+ return $this;
2189
+ } else {
2190
+ return call_user_func_array([$query, $method], $args);
2191
+ }
2192
+ }
2193
+
2194
+ public static function __callStatic($method, $args)
2195
+ {
2196
+ $model = new static();
2197
+ $query = $model->db();
2198
+ if (method_exists($model, 'scope' . $method)) {
2199
+ // 动态调用命名范围
2200
+ $method = 'scope' . $method;
2201
+ array_unshift($args, $query);
2202
+
2203
+ call_user_func_array([$model, $method], $args);
2204
+ return $query;
2205
+ } else {
2206
+ return call_user_func_array([$query, $method], $args);
2207
+ }
2208
+ }
2209
+
2210
+ /**
2211
+ * 修改器 设置数据对象的值
2212
+ * @access public
2213
+ * @param string $name 名称
2214
+ * @param mixed $value 值
2215
+ * @return void
2216
+ */
2217
+ public function __set($name, $value)
2218
+ {
2219
+ $this->setAttr($name, $value);
2220
+ }
2221
+
2222
+ /**
2223
+ * 获取器 获取数据对象的值
2224
+ * @access public
2225
+ * @param string $name 名称
2226
+ * @return mixed
2227
+ */
2228
+ public function __get($name)
2229
+ {
2230
+ return $this->getAttr($name);
2231
+ }
2232
+
2233
+ /**
2234
+ * 检测数据对象的值
2235
+ * @access public
2236
+ * @param string $name 名称
2237
+ * @return boolean
2238
+ */
2239
+ public function __isset($name)
2240
+ {
2241
+ try {
2242
+ if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) {
2243
+ return true;
2244
+ } else {
2245
+ $this->getAttr($name);
2246
+ return true;
2247
+ }
2248
+ } catch (InvalidArgumentException $e) {
2249
+ return false;
2250
+ }
2251
+
2252
+ }
2253
+
2254
+ /**
2255
+ * 销毁数据对象的值
2256
+ * @access public
2257
+ * @param string $name 名称
2258
+ * @return void
2259
+ */
2260
+ public function __unset($name)
2261
+ {
2262
+ unset($this->data[$name], $this->relation[$name]);
2263
+ }
2264
+
2265
+ public function __toString()
2266
+ {
2267
+ return $this->toJson();
2268
+ }
2269
+
2270
+ // JsonSerializable
2271
+ public function jsonSerialize()
2272
+ {
2273
+ return $this->toArray();
2274
+ }
2275
+
2276
+ // ArrayAccess
2277
+ public function offsetSet($name, $value)
2278
+ {
2279
+ $this->setAttr($name, $value);
2280
+ }
2281
+
2282
+ public function offsetExists($name)
2283
+ {
2284
+ return $this->__isset($name);
2285
+ }
2286
+
2287
+ public function offsetUnset($name)
2288
+ {
2289
+ $this->__unset($name);
2290
+ }
2291
+
2292
+ public function offsetGet($name)
2293
+ {
2294
+ return $this->getAttr($name);
2295
+ }
2296
+
2297
+ /**
2298
+ * 解序列化后处理
2299
+ */
2300
+ public function __wakeup()
2301
+ {
2302
+ $this->initialize();
2303
+ }
2304
+
2305
+ /**
2306
+ * 模型事件快捷方法
2307
+ * @param $callback
2308
+ * @param bool $override
2309
+ */
2310
+ protected static function beforeInsert($callback, $override = false)
2311
+ {
2312
+ self::event('before_insert', $callback, $override);
2313
+ }
2314
+
2315
+ protected static function afterInsert($callback, $override = false)
2316
+ {
2317
+ self::event('after_insert', $callback, $override);
2318
+ }
2319
+
2320
+ protected static function beforeUpdate($callback, $override = false)
2321
+ {
2322
+ self::event('before_update', $callback, $override);
2323
+ }
2324
+
2325
+ protected static function afterUpdate($callback, $override = false)
2326
+ {
2327
+ self::event('after_update', $callback, $override);
2328
+ }
2329
+
2330
+ protected static function beforeWrite($callback, $override = false)
2331
+ {
2332
+ self::event('before_write', $callback, $override);
2333
+ }
2334
+
2335
+ protected static function afterWrite($callback, $override = false)
2336
+ {
2337
+ self::event('after_write', $callback, $override);
2338
+ }
2339
+
2340
+ protected static function beforeDelete($callback, $override = false)
2341
+ {
2342
+ self::event('before_delete', $callback, $override);
2343
+ }
2344
+
2345
+ protected static function afterDelete($callback, $override = false)
2346
+ {
2347
+ self::event('after_delete', $callback, $override);
2348
+ }
2349
+
2350
+ }
thinkphp/library/think/Paginator.php ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: zhangyajun <448901948@qq.com>
10
+ // +----------------------------------------------------------------------
11
+
12
+ namespace think;
13
+
14
+ use ArrayAccess;
15
+ use ArrayIterator;
16
+ use Countable;
17
+ use IteratorAggregate;
18
+ use JsonSerializable;
19
+ use Traversable;
20
+
21
+ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
22
+ {
23
+ /** @var bool 是否为简洁模式 */
24
+ protected $simple = false;
25
+
26
+ /** @var Collection 数据集 */
27
+ protected $items;
28
+
29
+ /** @var integer 当前页 */
30
+ protected $currentPage;
31
+
32
+ /** @var integer 最后一页 */
33
+ protected $lastPage;
34
+
35
+ /** @var integer|null 数据总数 */
36
+ protected $total;
37
+
38
+ /** @var integer 每页的数量 */
39
+ protected $listRows;
40
+
41
+ /** @var bool 是否有下一页 */
42
+ protected $hasMore;
43
+
44
+ /** @var array 一些配置 */
45
+ protected $options = [
46
+ 'var_page' => 'page',
47
+ 'path' => '/',
48
+ 'query' => [],
49
+ 'fragment' => '',
50
+ ];
51
+
52
+ /** @var mixed simple模式下的下个元素 */
53
+ protected $nextItem;
54
+
55
+ public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
56
+ {
57
+ $this->options = array_merge($this->options, $options);
58
+
59
+ $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path'];
60
+
61
+ $this->simple = $simple;
62
+ $this->listRows = $listRows;
63
+
64
+ if (!$items instanceof Collection) {
65
+ $items = Collection::make($items);
66
+ }
67
+
68
+ if ($simple) {
69
+ $this->currentPage = $this->setCurrentPage($currentPage);
70
+ $this->hasMore = count($items) > ($this->listRows);
71
+ if ($this->hasMore) {
72
+ $this->nextItem = $items->slice($this->listRows, 1);
73
+ }
74
+ $items = $items->slice(0, $this->listRows);
75
+ } else {
76
+ $this->total = $total;
77
+ $this->lastPage = (int) ceil($total / $listRows);
78
+ $this->currentPage = $this->setCurrentPage($currentPage);
79
+ $this->hasMore = $this->currentPage < $this->lastPage;
80
+ }
81
+ $this->items = $items;
82
+ }
83
+
84
+ /**
85
+ * @param $items
86
+ * @param $listRows
87
+ * @param null $currentPage
88
+ * @param bool $simple
89
+ * @param null $total
90
+ * @param array $options
91
+ * @return Paginator
92
+ */
93
+ public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
94
+ {
95
+ return new static($items, $listRows, $currentPage, $total, $simple, $options);
96
+ }
97
+
98
+ protected function setCurrentPage($currentPage)
99
+ {
100
+ if (!$this->simple && $currentPage > $this->lastPage) {
101
+ return $this->lastPage > 0 ? $this->lastPage : 1;
102
+ }
103
+
104
+ return $currentPage;
105
+ }
106
+
107
+ /**
108
+ * 获取页码对应的链接
109
+ *
110
+ * @param $page
111
+ * @return string
112
+ */
113
+ protected function url($page)
114
+ {
115
+ if ($page <= 0) {
116
+ $page = 1;
117
+ }
118
+
119
+ if (strpos($this->options['path'], '[PAGE]') === false) {
120
+ $parameters = [$this->options['var_page'] => $page];
121
+ $path = $this->options['path'];
122
+ } else {
123
+ $parameters = [];
124
+ $path = str_replace('[PAGE]', $page, $this->options['path']);
125
+ }
126
+ if (count($this->options['query']) > 0) {
127
+ $parameters = array_merge($this->options['query'], $parameters);
128
+ }
129
+ $url = $path;
130
+ if (!empty($parameters)) {
131
+ $url .= '?' . http_build_query($parameters, null, '&');
132
+ }
133
+ return $url . $this->buildFragment();
134
+ }
135
+
136
+ /**
137
+ * 自动获取当前页码
138
+ * @param string $varPage
139
+ * @param int $default
140
+ * @return int
141
+ */
142
+ public static function getCurrentPage($varPage = 'page', $default = 1)
143
+ {
144
+ $page = (int) Request::instance()->param($varPage);
145
+
146
+ if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) {
147
+ return $page;
148
+ }
149
+
150
+ return $default;
151
+ }
152
+
153
+ /**
154
+ * 自动获取当前的path
155
+ * @return string
156
+ */
157
+ public static function getCurrentPath()
158
+ {
159
+ return Request::instance()->baseUrl();
160
+ }
161
+
162
+ public function total()
163
+ {
164
+ if ($this->simple) {
165
+ throw new \DomainException('not support total');
166
+ }
167
+ return $this->total;
168
+ }
169
+
170
+ public function listRows()
171
+ {
172
+ return $this->listRows;
173
+ }
174
+
175
+ public function currentPage()
176
+ {
177
+ return $this->currentPage;
178
+ }
179
+
180
+ public function lastPage()
181
+ {
182
+ if ($this->simple) {
183
+ throw new \DomainException('not support last');
184
+ }
185
+ return $this->lastPage;
186
+ }
187
+
188
+ /**
189
+ * 数据是否足够分页
190
+ * @return boolean
191
+ */
192
+ public function hasPages()
193
+ {
194
+ return !(1 == $this->currentPage && !$this->hasMore);
195
+ }
196
+
197
+ /**
198
+ * 创建一组分页链接
199
+ *
200
+ * @param int $start
201
+ * @param int $end
202
+ * @return array
203
+ */
204
+ public function getUrlRange($start, $end)
205
+ {
206
+ $urls = [];
207
+
208
+ for ($page = $start; $page <= $end; $page++) {
209
+ $urls[$page] = $this->url($page);
210
+ }
211
+
212
+ return $urls;
213
+ }
214
+
215
+ /**
216
+ * 设置URL锚点
217
+ *
218
+ * @param string|null $fragment
219
+ * @return $this
220
+ */
221
+ public function fragment($fragment)
222
+ {
223
+ $this->options['fragment'] = $fragment;
224
+ return $this;
225
+ }
226
+
227
+ /**
228
+ * 添加URL参数
229
+ *
230
+ * @param array|string $key
231
+ * @param string|null $value
232
+ * @return $this
233
+ */
234
+ public function appends($key, $value = null)
235
+ {
236
+ if (!is_array($key)) {
237
+ $queries = [$key => $value];
238
+ } else {
239
+ $queries = $key;
240
+ }
241
+
242
+ foreach ($queries as $k => $v) {
243
+ if ($k !== $this->options['var_page']) {
244
+ $this->options['query'][$k] = $v;
245
+ }
246
+ }
247
+
248
+ return $this;
249
+ }
250
+
251
+ /**
252
+ * 构造锚点字符串
253
+ *
254
+ * @return string
255
+ */
256
+ protected function buildFragment()
257
+ {
258
+ return $this->options['fragment'] ? '#' . $this->options['fragment'] : '';
259
+ }
260
+
261
+ /**
262
+ * 渲染分页html
263
+ * @return mixed
264
+ */
265
+ abstract public function render();
266
+
267
+ public function items()
268
+ {
269
+ return $this->items->all();
270
+ }
271
+
272
+ public function getCollection()
273
+ {
274
+ return $this->items;
275
+ }
276
+
277
+ public function isEmpty()
278
+ {
279
+ return $this->items->isEmpty();
280
+ }
281
+
282
+ /**
283
+ * 给每个元素执行个回调
284
+ *
285
+ * @param callable $callback
286
+ * @return $this
287
+ */
288
+ public function each(callable $callback)
289
+ {
290
+ foreach ($this->items as $key => $item) {
291
+ $result = $callback($item, $key);
292
+ if (false === $result) {
293
+ break;
294
+ } elseif (!is_object($item)) {
295
+ $this->items[$key] = $result;
296
+ }
297
+ }
298
+
299
+ return $this;
300
+ }
301
+
302
+ /**
303
+ * Retrieve an external iterator
304
+ * @return Traversable An instance of an object implementing <b>Iterator</b> or
305
+ * <b>Traversable</b>
306
+ */
307
+ public function getIterator()
308
+ {
309
+ return new ArrayIterator($this->items->all());
310
+ }
311
+
312
+ /**
313
+ * Whether a offset exists
314
+ * @param mixed $offset
315
+ * @return bool
316
+ */
317
+ public function offsetExists($offset)
318
+ {
319
+ return $this->items->offsetExists($offset);
320
+ }
321
+
322
+ /**
323
+ * Offset to retrieve
324
+ * @param mixed $offset
325
+ * @return mixed
326
+ */
327
+ public function offsetGet($offset)
328
+ {
329
+ return $this->items->offsetGet($offset);
330
+ }
331
+
332
+ /**
333
+ * Offset to set
334
+ * @param mixed $offset
335
+ * @param mixed $value
336
+ */
337
+ public function offsetSet($offset, $value)
338
+ {
339
+ $this->items->offsetSet($offset, $value);
340
+ }
341
+
342
+ /**
343
+ * Offset to unset
344
+ * @param mixed $offset
345
+ * @return void
346
+ * @since 5.0.0
347
+ */
348
+ public function offsetUnset($offset)
349
+ {
350
+ $this->items->offsetUnset($offset);
351
+ }
352
+
353
+ /**
354
+ * Count elements of an object
355
+ */
356
+ public function count()
357
+ {
358
+ return $this->items->count();
359
+ }
360
+
361
+ public function __toString()
362
+ {
363
+ return (string) $this->render();
364
+ }
365
+
366
+ public function toArray()
367
+ {
368
+ if ($this->simple) {
369
+ return [
370
+ 'per_page' => $this->listRows,
371
+ 'current_page' => $this->currentPage,
372
+ 'has_more' => $this->hasMore,
373
+ 'next_item' => $this->nextItem,
374
+ 'data' => $this->items->toArray(),
375
+ ];
376
+ } else {
377
+ return [
378
+ 'total' => $this->total,
379
+ 'per_page' => $this->listRows,
380
+ 'current_page' => $this->currentPage,
381
+ 'last_page' => $this->lastPage,
382
+ 'data' => $this->items->toArray(),
383
+ ];
384
+ }
385
+
386
+ }
387
+
388
+ /**
389
+ * Specify data which should be serialized to JSON
390
+ */
391
+ public function jsonSerialize()
392
+ {
393
+ return $this->toArray();
394
+ }
395
+
396
+ public function __call($name, $arguments)
397
+ {
398
+ $collection = $this->getCollection();
399
+
400
+ $result = call_user_func_array([$collection, $name], $arguments);
401
+
402
+ if ($result === $collection) {
403
+ return $this;
404
+ }
405
+
406
+ return $result;
407
+ }
408
+
409
+ }
thinkphp/library/think/Process.php ADDED
@@ -0,0 +1,1205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // +----------------------------------------------------------------------
3
+ // | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
+ // +----------------------------------------------------------------------
5
+ // | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
6
+ // +----------------------------------------------------------------------
7
+ // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+ // +----------------------------------------------------------------------
9
+ // | Author: yunwuxin <448901948@qq.com>
10
+ // +----------------------------------------------------------------------
11
+
12
+ namespace think;
13
+
14
+ use think\process\exception\Failed as ProcessFailedException;
15
+ use think\process\exception\Timeout as ProcessTimeoutException;
16
+ use think\process\pipes\Pipes;
17
+ use think\process\pipes\Unix as UnixPipes;
18
+ use think\process\pipes\Windows as WindowsPipes;
19
+ use think\process\Utils;
20
+
21
+ class Process
22
+ {
23
+
24
+ const ERR = 'err';
25
+ const OUT = 'out';
26
+
27
+ const STATUS_READY = 'ready';
28
+ const STATUS_STARTED = 'started';
29
+ const STATUS_TERMINATED = 'terminated';
30
+
31
+ const STDIN = 0;
32
+ const STDOUT = 1;
33
+ const STDERR = 2;
34
+
35
+ const TIMEOUT_PRECISION = 0.2;
36
+
37
+ private $callback;
38
+ private $commandline;
39
+ private $cwd;
40
+ private $env;
41
+ private $input;
42
+ private $starttime;
43
+ private $lastOutputTime;
44
+ private $timeout;
45
+ private $idleTimeout;
46
+ private $options;
47
+ private $exitcode;
48
+ private $fallbackExitcode;
49
+ private $processInformation;
50
+ private $outputDisabled = false;
51
+ private $stdout;
52
+ private $stderr;
53
+ private $enhanceWindowsCompatibility = true;
54
+ private $enhanceSigchildCompatibility;
55
+ private $process;
56
+ private $status = self::STATUS_READY;
57
+ private $incrementalOutputOffset = 0;
58
+ private $incrementalErrorOutputOffset = 0;
59
+ private $tty;
60
+ private $pty;
61
+
62
+ private $useFileHandles = false;
63
+
64
+ /** @var Pipes */
65
+ private $processPipes;
66
+
67
+ private $latestSignal;
68
+
69
+ private static $sigchild;
70
+
71
+ /**
72
+ * @var array
73
+ */
74
+ public static $exitCodes = [
75
+ 0 => 'OK',
76
+ 1 => 'General error',
77
+ 2 => 'Misuse of shell builtins',
78
+ 126 => 'Invoked command cannot execute',
79
+ 127 => 'Command not found',
80
+ 128 => 'Invalid exit argument',
81
+ // signals
82
+ 129 => 'Hangup',
83
+ 130 => 'Interrupt',
84
+ 131 => 'Quit and dump core',
85
+ 132 => 'Illegal instruction',
86
+ 133 => 'Trace/breakpoint trap',
87
+ 134 => 'Process aborted',
88
+ 135 => 'Bus error: "access to undefined portion of memory object"',
89
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
90
+ 137 => 'Kill (terminate immediately)',
91
+ 138 => 'User-defined 1',
92
+ 139 => 'Segmentation violation',
93
+ 140 => 'User-defined 2',
94
+ 141 => 'Write to pipe with no one reading',
95
+ 142 => 'Signal raised by alarm',
96
+ 143 => 'Termination (request to terminate)',
97
+ // 144 - not defined
98
+ 145 => 'Child process terminated, stopped (or continued*)',
99
+ 146 => 'Continue if stopped',
100
+ 147 => 'Stop executing temporarily',
101
+ 148 => 'Terminal stop signal',
102
+ 149 => 'Background process attempting to read from tty ("in")',
103
+ 150 => 'Background process attempting to write to tty ("out")',
104
+ 151 => 'Urgent data available on socket',
105
+ 152 => 'CPU time limit exceeded',
106
+ 153 => 'File size limit exceeded',
107
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
108
+ 155 => 'Profiling timer expired',
109
+ // 156 - not defined
110
+ 157 => 'Pollable event',
111
+ // 158 - not defined
112
+ 159 => 'Bad syscall',
113
+ ];
114
+
115
+ /**
116
+ * 构造方法
117
+ * @param string $commandline 指令
118
+ * @param string|null $cwd 工作目录
119
+ * @param array|null $env 环境变量
120
+ * @param string|null $input 输入
121
+ * @param int|float|null $timeout 超时时间
122
+ * @param array $options proc_open的选项
123
+ * @throws \RuntimeException
124
+ * @api
125
+ */
126
+ public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = [])
127
+ {
128
+ if (!function_exists('proc_open')) {
129
+ throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
130
+ }
131
+
132
+ $this->commandline = $commandline;
133
+ $this->cwd = $cwd;
134
+
135
+ if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) {
136
+ $this->cwd = getcwd();
137
+ }
138
+ if (null !== $env) {
139
+ $this->setEnv($env);
140
+ }
141
+
142
+ $this->input = $input;
143
+ $this->setTimeout($timeout);
144
+ $this->useFileHandles = '\\' === DS;
145
+ $this->pty = false;
146
+ $this->enhanceWindowsCompatibility = true;
147
+ $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled();
148
+ $this->options = array_replace([
149
+ 'suppress_errors' => true,
150
+ 'binary_pipes' => true,
151
+ ], $options);
152
+ }
153
+
154
+ public function __destruct()
155
+ {
156
+ $this->stop();
157
+ }
158
+
159
+ public function __clone()
160
+ {
161
+ $this->resetProcessData();
162
+ }
163
+
164
+ /**
165
+ * 运行指令
166
+ * @param callback|null $callback
167
+ * @return int
168
+ */
169
+ public function run($callback = null)
170
+ {
171
+ $this->start($callback);
172
+
173
+ return $this->wait();
174
+ }
175
+
176
+ /**
177
+ * 运行指令
178
+ * @param callable|null $callback
179
+ * @return self
180
+ * @throws \RuntimeException
181
+ * @throws ProcessFailedException
182
+ */
183
+ public function mustRun($callback = null)
184
+ {
185
+ if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
186
+ throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
187
+ }
188
+
189
+ if (0 !== $this->run($callback)) {
190
+ throw new ProcessFailedException($this);
191
+ }
192
+
193
+ return $this;
194
+ }
195
+
196
+ /**
197
+ * 启动进程并写到 STDIN 输入后返回。
198
+ * @param callable|null $callback
199
+ * @throws \RuntimeException
200
+ * @throws \RuntimeException
201
+ * @throws \LogicException
202
+ */
203
+ public function start($callback = null)
204
+ {
205
+ if ($this->isRunning()) {
206
+ throw new \RuntimeException('Process is already running');
207
+ }
208
+ if ($this->outputDisabled && null !== $callback) {
209
+ throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.');
210
+ }
211
+
212
+ $this->resetProcessData();
213
+ $this->starttime = $this->lastOutputTime = microtime(true);
214
+ $this->callback = $this->buildCallback($callback);
215
+ $descriptors = $this->getDescriptors();
216
+
217
+ $commandline = $this->commandline;
218
+
219
+ if ('\\' === DS && $this->enhanceWindowsCompatibility) {
220
+ $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')';
221
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
222
+ $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename);
223
+ }
224
+ $commandline .= '"';
225
+
226
+ if (!isset($this->options['bypass_shell'])) {
227
+ $this->options['bypass_shell'] = true;
228
+ }
229
+ }
230
+
231
+ $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
232
+
233
+ if (!is_resource($this->process)) {
234
+ throw new \RuntimeException('Unable to launch a new process.');
235
+ }
236
+ $this->status = self::STATUS_STARTED;
237
+
238
+ if ($this->tty) {
239
+ return;
240
+ }
241
+
242
+ $this->updateStatus(false);
243
+ $this->checkTimeout();
244
+ }
245
+
246
+ /**
247
+ * 重启进程
248
+ * @param callable|null $callback
249
+ * @return Process
250
+ * @throws \RuntimeException
251
+ * @throws \RuntimeException
252
+ */
253
+ public function restart($callback = null)
254
+ {
255
+ if ($this->isRunning()) {
256
+ throw new \RuntimeException('Process is already running');
257
+ }
258
+
259
+ $process = clone $this;
260
+ $process->start($callback);
261
+
262
+ return $process;
263
+ }
264
+
265
+ /**
266
+ * 等待要终止的进程
267
+ * @param callable|null $callback
268
+ * @return int
269
+ */
270
+ public function wait($callback = null)
271
+ {
272
+ $this->requireProcessIsStarted(__FUNCTION__);
273
+
274
+ $this->updateStatus(false);
275
+ if (null !== $callback) {
276
+ $this->callback = $this->buildCallback($callback);
277
+ }
278
+
279
+ do {
280
+ $this->checkTimeout();
281
+ $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen();
282
+ $close = '\\' !== DS || !$running;
283
+ $this->readPipes(true, $close);
284
+ } while ($running);
285
+
286
+ while ($this->isRunning()) {
287
+ usleep(1000);
288
+ }
289
+
290
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
291
+ throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
292
+ }
293
+
294
+ return $this->exitcode;
295
+ }
296
+
297
+ /**
298
+ * 获取PID
299
+ * @return int|null
300
+ * @throws \RuntimeException
301
+ */
302
+ public function getPid()
303
+ {
304
+ if ($this->isSigchildEnabled()) {
305
+ throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
306
+ }
307
+
308
+ $this->updateStatus(false);
309
+
310
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
311
+ }
312
+
313
+ /**
314
+ * 将一个 POSIX 信号发送到进程中
315
+ * @param int $signal
316
+ * @return Process
317
+ */
318
+ public function signal($signal)
319
+ {
320
+ $this->doSignal($signal, true);
321
+
322
+ return $this;
323
+ }
324
+
325
+ /**
326
+ * 禁用从底层过程获取输出和错误输出。
327
+ * @return Process
328
+ */
329
+ public function disableOutput()
330
+ {
331
+ if ($this->isRunning()) {
332
+ throw new \RuntimeException('Disabling output while the process is running is not possible.');
333
+ }
334
+ if (null !== $this->idleTimeout) {
335
+ throw new \LogicException('Output can not be disabled while an idle timeout is set.');
336
+ }
337
+
338
+ $this->outputDisabled = true;
339
+
340
+ return $this;
341
+ }
342
+
343
+ /**
344
+ * 开启从底层过程获取输出和错误输出。
345
+ * @return Process
346
+ * @throws \RuntimeException
347
+ */
348
+ public function enableOutput()
349
+ {
350
+ if ($this->isRunning()) {
351
+ throw new \RuntimeException('Enabling output while the process is running is not possible.');
352
+ }
353
+
354
+ $this->outputDisabled = false;
355
+
356
+ return $this;
357
+ }
358
+
359
+ /**
360
+ * 输出是否禁用
361
+ * @return bool
362
+ */
363
+ public function isOutputDisabled()
364
+ {
365
+ return $this->outputDisabled;
366
+ }
367
+
368
+ /**
369
+ * 获取当前的输出管道
370
+ * @return string
371
+ * @throws \LogicException
372
+ * @throws \LogicException
373
+ * @api
374
+ */
375
+ public function getOutput()
376
+ {
377
+ if ($this->outputDisabled) {
378
+ throw new \LogicException('Output has been disabled.');
379
+ }
380
+
381
+ $this->requireProcessIsStarted(__FUNCTION__);
382
+
383
+ $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true);
384
+
385
+ return $this->stdout;
386
+ }
387
+
388
+ /**
389
+ * 以增量方式返回的输出结果。
390
+ * @return string
391
+ */
392
+ public function getIncrementalOutput()
393
+ {
394
+ $this->requireProcessIsStarted(__FUNCTION__);
395
+
396
+ $data = $this->getOutput();
397
+
398
+ $latest = substr($data, $this->incrementalOutputOffset);
399
+
400
+ if (false === $latest) {
401
+ return '';
402
+ }
403
+
404
+ $this->incrementalOutputOffset = strlen($data);
405
+
406
+ return $latest;
407
+ }
408
+
409
+ /**
410
+ * 清空输出
411
+ * @return Process
412
+ */
413
+ public function clearOutput()
414
+ {
415
+ $this->stdout = '';
416
+ $this->incrementalOutputOffset = 0;
417
+
418
+ return $this;
419
+ }
420
+
421
+ /**
422
+ * 返回当前的错误输出的过程 (STDERR)。
423
+ * @return string
424
+ */
425
+ public function getErrorOutput()
426
+ {
427
+ if ($this->outputDisabled) {
428
+ throw new \LogicException('Output has been disabled.');
429
+ }
430
+
431
+ $this->requireProcessIsStarted(__FUNCTION__);
432
+
433
+ $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true);
434
+
435
+ return $this->stderr;
436
+ }
437
+
438
+ /**
439
+ * 以增量方式返回 errorOutput
440
+ * @return string
441
+ */
442
+ public function getIncrementalErrorOutput()
443
+ {
444
+ $this->requireProcessIsStarted(__FUNCTION__);
445
+
446
+ $data = $this->getErrorOutput();
447
+
448
+ $latest = substr($data, $this->incrementalErrorOutputOffset);
449
+
450
+ if (false === $latest) {
451
+ return '';
452
+ }
453
+
454
+ $this->incrementalErrorOutputOffset = strlen($data);
455
+
456
+ return $latest;
457
+ }
458
+
459
+ /**
460
+ * 清空 errorOutput
461
+ * @return Process
462
+ */
463
+ public function clearErrorOutput()
464
+ {
465
+ $this->stderr = '';
466
+ $this->incrementalErrorOutputOffset = 0;
467
+
468
+ return $this;
469
+ }
470
+
471
+ /**
472
+ * 获取退出码
473
+ * @return null|int
474
+ */
475
+ public function getExitCode()
476
+ {
477
+ if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
478
+ throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
479
+ }
480
+
481
+ $this->updateStatus(false);
482
+
483
+ return $this->exitcode;
484
+ }
485
+
486
+ /**
487
+ * 获取退出文本
488
+ * @return null|string
489
+ */
490
+ public function getExitCodeText()
491
+ {
492
+ if (null === $exitcode = $this->getExitCode()) {
493
+ return;
494
+ }
495
+
496
+ return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
497
+ }
498
+
499
+ /**
500
+ * 检查是否成功
501
+ * @return bool
502
+ */
503
+ public function isSuccessful()
504
+ {
505
+ return 0 === $this->getExitCode();
506
+ }
507
+
508
+ /**
509
+ * 是否未捕获的信号已被终止子进程
510
+ * @return bool
511
+ */
512
+ public function hasBeenSignaled()
513
+ {
514
+ $this->requireProcessIsTerminated(__FUNCTION__);
515
+
516
+ if ($this->isSigchildEnabled()) {
517
+ throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
518
+ }
519
+
520
+ $this->updateStatus(false);
521
+
522
+ return $this->processInformation['signaled'];
523
+ }
524
+
525
+ /**
526
+ * 返回导致子进程终止其执行的数。
527
+ * @return int
528
+ */
529
+ public function getTermSignal()
530
+ {
531
+ $this->requireProcessIsTerminated(__FUNCTION__);
532
+
533
+ if ($this->isSigchildEnabled()) {
534
+ throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
535
+ }
536
+
537
+ $this->updateStatus(false);
538
+
539
+ return $this->processInformation['termsig'];
540
+ }
541
+
542
+ /**
543
+ * 检查子进程信号是否已停止
544
+ * @return bool
545
+ */
546
+ public function hasBeenStopped()
547
+ {
548
+ $this->requireProcessIsTerminated(__FUNCTION__);
549
+
550
+ $this->updateStatus(false);
551
+
552
+ return $this->processInformation['stopped'];
553
+ }
554
+
555
+ /**
556
+ * 返回导致子进程停止其执行的数。
557
+ * @return int
558
+ */
559
+ public function getStopSignal()
560
+ {
561
+ $this->requireProcessIsTerminated(__FUNCTION__);
562
+
563
+ $this->updateStatus(false);
564
+
565
+ return $this->processInformation['stopsig'];
566
+ }
567
+
568
+ /**
569
+ * 检查是否正在运行
570
+ * @return bool
571
+ */
572
+ public function isRunning()
573
+ {
574
+ if (self::STATUS_STARTED !== $this->status) {
575
+ return false;
576
+ }
577
+
578
+ $this->updateStatus(false);
579
+
580
+ return $this->processInformation['running'];
581
+ }
582
+
583
+ /**
584
+ * 检查是否已开始
585
+ * @return bool
586
+ */
587
+ public function isStarted()
588
+ {
589
+ return self::STATUS_READY != $this->status;
590
+ }
591
+
592
+ /**
593
+ * 检查是否已终止
594
+ * @return bool
595
+ */
596
+ public function isTerminated()
597
+ {
598
+ $this->updateStatus(false);
599
+
600
+ return self::STATUS_TERMINATED == $this->status;
601
+ }
602
+
603
+ /**
604
+ * 获取当前的状态
605
+ * @return string
606
+ */
607
+ public function getStatus()
608
+ {
609
+ $this->updateStatus(false);
610
+
611
+ return $this->status;
612
+ }
613
+
614
+ /**
615
+ * 终止进程
616
+ */
617
+ public function stop()
618
+ {
619
+ if ($this->isRunning()) {
620
+ if ('\\' === DS && !$this->isSigchildEnabled()) {
621
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode);
622
+ if ($exitCode > 0) {
623
+ throw new \RuntimeException('Unable to kill the process');
624
+ }
625
+ } else {
626
+ $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`);
627
+ foreach ($pids as $pid) {
628
+ if (is_numeric($pid)) {
629
+ posix_kill($pid, 9);
630
+ }
631
+ }
632
+ }
633
+ }
634
+
635
+ $this->updateStatus(false);
636
+ if ($this->processInformation['running']) {
637
+ $this->close();
638
+ }
639
+
640
+ return $this->exitcode;
641
+ }
642
+
643
+ /**
644
+ * 添加一行输出
645
+ * @param string $line
646
+ */
647
+ public function addOutput($line)
648
+ {
649
+ $this->lastOutputTime = microtime(true);
650
+ $this->stdout .= $line;
651
+ }
652
+
653
+ /**
654
+ * 添加一行错误输出
655
+ * @param string $line
656
+ */
657
+ public function addErrorOutput($line)
658
+ {
659
+ $this->lastOutputTime = microtime(true);
660
+ $this->stderr .= $line;
661
+ }
662
+
663
+ /**
664
+ * 获取被执行的指令
665
+ * @return string
666
+ */
667
+ public function getCommandLine()
668
+ {
669
+ return $this->commandline;
670
+ }
671
+
672
+ /**
673
+ * 设置指令
674
+ * @param string $commandline
675
+ * @return self
676
+ */
677
+ public function setCommandLine($commandline)
678
+ {
679
+ $this->commandline = $commandline;
680
+
681
+ return $this;
682
+ }
683
+
684
+ /**
685
+ * 获取超时时间
686
+ * @return float|null
687
+ */
688
+ public function getTimeout()
689
+ {
690
+ return $this->timeout;
691
+ }
692
+
693
+ /**
694
+ * 获取idle超时时间
695
+ * @return float|null
696
+ */
697
+ public function getIdleTimeout()
698
+ {
699
+ return $this->idleTimeout;
700
+ }
701
+
702
+ /**
703
+ * 设置超时时间
704
+ * @param int|float|null $timeout
705
+ * @return self
706
+ */
707
+ public function setTimeout($timeout)
708
+ {
709
+ $this->timeout = $this->validateTimeout($timeout);
710
+
711
+ return $this;
712
+ }
713
+
714
+ /**
715
+ * 设置idle超时时间
716
+ * @param int|float|null $timeout
717
+ * @return self
718
+ */
719
+ public function setIdleTimeout($timeout)
720
+ {
721
+ if (null !== $timeout && $this->outputDisabled) {
722
+ throw new \LogicException('Idle timeout can not be set while the output is disabled.');
723
+ }
724
+
725
+ $this->idleTimeout = $this->validateTimeout($timeout);
726
+
727
+ return $this;
728
+ }
729
+
730
+ /**
731
+ * 设置TTY
732
+ * @param bool $tty
733
+ * @return self
734
+ */
735
+ public function setTty($tty)
736
+ {
737
+ if ('\\' === DS && $tty) {
738
+ throw new \RuntimeException('TTY mode is not supported on Windows platform.');
739
+ }
740
+ if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) {
741
+ throw new \RuntimeException('TTY mode requires /dev/tty to be readable.');
742
+ }
743
+
744
+ $this->tty = (bool) $tty;
745
+
746
+ return $this;
747
+ }
748
+
749
+ /**
750
+ * 检查是否是tty模式
751
+ * @return bool
752
+ */
753
+ public function isTty()
754
+ {
755
+ return $this->tty;
756
+ }
757
+
758
+ /**
759
+ * 设置pty模式
760
+ * @param bool $bool
761
+ * @return self
762
+ */
763
+ public function setPty($bool)
764
+ {
765
+ $this->pty = (bool) $bool;
766
+
767
+ return $this;
768
+ }
769
+
770
+ /**
771
+ * 是否是pty模式
772
+ * @return bool
773
+ */
774
+ public function isPty()
775
+ {
776
+ return $this->pty;
777
+ }
778
+
779
+ /**
780
+ * 获取工作目录
781
+ * @return string|null
782
+ */
783
+ public function getWorkingDirectory()
784
+ {
785
+ if (null === $this->cwd) {
786
+ return getcwd() ?: null;
787
+ }
788
+
789
+ return $this->cwd;
790
+ }
791
+
792
+ /**
793
+ * 设置工作目录
794
+ * @param string $cwd
795
+ * @return self
796
+ */
797
+ public function setWorkingDirectory($cwd)
798
+ {
799
+ $this->cwd = $cwd;
800
+
801
+ return $this;
802
+ }
803
+
804
+ /**
805
+ * 获取环境变量
806
+ * @return array
807
+ */
808
+ public function getEnv()
809
+ {
810
+ return $this->env;
811
+ }
812
+
813
+ /**
814
+ * 设置环境变量
815
+ * @param array $env
816
+ * @return self
817
+ */
818
+ public function setEnv(array $env)
819
+ {
820
+ $env = array_filter($env, function ($value) {
821
+ return !is_array($value);
822
+ });
823
+
824
+ $this->env = [];
825
+ foreach ($env as $key => $value) {
826
+ $this->env[(binary) $key] = (binary) $value;
827
+ }
828
+
829
+ return $this;
830
+ }
831
+
832
+ /**
833
+ * 获取输入
834
+ * @return null|string
835
+ */
836
+ public function getInput()
837
+ {
838
+ return $this->input;
839
+ }
840
+
841
+ /**
842
+ * 设置输入
843
+ * @param mixed $input
844
+ * @return self
845
+ */
846
+ public function setInput($input)
847
+ {
848
+ if ($this->isRunning()) {
849
+ throw new \LogicException('Input can not be set while the process is running.');
850
+ }
851
+
852
+ $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input);
853
+
854
+ return $this;
855
+ }
856
+
857
+ /**
858
+ * 获取proc_open的选项
859
+ * @return array
860
+ */
861
+ public function getOptions()
862
+ {
863
+ return $this->options;
864
+ }
865
+
866
+ /**
867
+ * 设置proc_open的选项
868
+ * @param array $options
869
+ * @return self
870
+ */
871
+ public function setOptions(array $options)
872
+ {
873
+ $this->options = $options;
874
+
875
+ return $this;
876
+ }
877
+
878
+ /**
879
+ * 是否兼容windows
880
+ * @return bool
881
+ */
882
+ public function getEnhanceWindowsCompatibility()
883
+ {
884
+ return $this->enhanceWindowsCompatibility;
885
+ }
886
+
887
+ /**
888
+ * 设置是否兼容windows
889
+ * @param bool $enhance
890
+ * @return self
891
+ */
892
+ public function setEnhanceWindowsCompatibility($enhance)
893
+ {
894
+ $this->enhanceWindowsCompatibility = (bool) $enhance;
895
+
896
+ return $this;
897
+ }
898
+
899
+ /**
900
+ * 返回是否 sigchild 兼容模式激活
901
+ * @return bool
902
+ */
903
+ public function getEnhanceSigchildCompatibility()
904
+ {
905
+ return $this->enhanceSigchildCompatibility;
906
+ }
907
+
908
+ /**
909
+ * 激活 sigchild 兼容性模式。
910
+ * @param bool $enhance
911
+ * @return self
912
+ */
913
+ public function setEnhanceSigchildCompatibility($enhance)
914
+ {
915
+ $this->enhanceSigchildCompatibility = (bool) $enhance;
916
+
917
+ return $this;
918
+ }
919
+
920
+ /**
921
+ * 是否超时
922
+ */
923
+ public function checkTimeout()
924
+ {
925
+ if (self::STATUS_STARTED !== $this->status) {
926
+ return;
927
+ }
928
+
929
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
930
+ $this->stop();
931
+
932
+ throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL);
933
+ }
934
+
935
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
936
+ $this->stop();
937
+
938
+ throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE);
939
+ }
940
+ }
941
+
942
+ /**
943
+ * 是否支持pty
944
+ * @return bool
945
+ */
946
+ public static function isPtySupported()
947
+ {
948
+ static $result;
949
+
950
+ if (null !== $result) {
951
+ return $result;
952
+ }
953
+
954
+ if ('\\' === DS) {
955
+ return $result = false;
956
+ }
957
+
958
+ $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes);
959
+ if (is_resource($proc)) {
960
+ proc_close($proc);
961
+
962
+ return $result = true;
963
+ }
964
+
965
+ return $result = false;
966
+ }
967
+
968
+ /**
969
+ * 创建所需的 proc_open 的描述符
970
+ * @return array
971
+ */
972
+ private function getDescriptors()
973
+ {
974
+ if ('\\' === DS) {
975
+ $this->processPipes = WindowsPipes::create($this, $this->input);
976
+ } else {
977
+ $this->processPipes = UnixPipes::create($this, $this->input);
978
+ }
979
+ $descriptors = $this->processPipes->getDescriptors($this->outputDisabled);
980
+
981
+ if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
982
+
983
+ $descriptors = array_merge($descriptors, [['pipe', 'w']]);
984
+
985
+ $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code';
986
+ }
987
+
988
+ return $descriptors;
989
+ }
990
+
991
+ /**
992
+ * 建立 wait () 使用的回调。
993
+ * @param callable|null $callback
994
+ * @return callable
995
+ */
996
+ protected function buildCallback($callback)
997
+ {
998
+ $out = self::OUT;
999
+ $callback = function ($type, $data) use ($callback, $out) {
1000
+ if ($out == $type) {
1001
+ $this->addOutput($data);
1002
+ } else {
1003
+ $this->addErrorOutput($data);
1004
+ }
1005
+
1006
+ if (null !== $callback) {
1007
+ call_user_func($callback, $type, $data);
1008
+ }
1009
+ };
1010
+
1011
+ return $callback;
1012
+ }
1013
+
1014
+ /**
1015
+ * 更新状态
1016
+ * @param bool $blocking
1017
+ */
1018
+ protected function updateStatus($blocking)
1019
+ {
1020
+ if (self::STATUS_STARTED !== $this->status) {
1021
+ return;
1022
+ }
1023
+
1024
+ $this->processInformation = proc_get_status($this->process);
1025
+ $this->captureExitCode();
1026
+
1027
+ $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true);
1028
+
1029
+ if (!$this->processInformation['running']) {
1030
+ $this->close();
1031
+ }
1032
+ }
1033
+
1034
+ /**
1035
+ * 是否开启 '--enable-sigchild'
1036
+ * @return bool
1037
+ */
1038
+ protected function isSigchildEnabled()
1039
+ {
1040
+ if (null !== self::$sigchild) {
1041
+ return self::$sigchild;
1042
+ }
1043
+
1044
+ if (!function_exists('phpinfo')) {
1045
+ return self::$sigchild = false;
1046
+ }
1047
+
1048
+ ob_start();
1049
+ phpinfo(INFO_GENERAL);
1050
+
1051
+ return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
1052
+ }
1053
+
1054
+ /**
1055
+ * 验证是否超时
1056
+ * @param int|float|null $timeout
1057
+ * @return float|null
1058
+ */
1059
+ private function validateTimeout($timeout)
1060
+ {
1061
+ $timeout = (float) $timeout;
1062
+
1063
+ if (0.0 === $timeout) {
1064
+ $timeout = null;
1065
+ } elseif ($timeout < 0) {
1066
+ throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
1067
+ }
1068
+
1069
+ return $timeout;
1070
+ }
1071
+
1072
+ /**
1073
+ * 读取pipes
1074
+ * @param bool $blocking
1075
+ * @param bool $close
1076
+ */
1077
+ private function readPipes($blocking, $close)
1078
+ {
1079
+ $result = $this->processPipes->readAndWrite($blocking, $close);
1080
+
1081
+ $callback = $this->callback;
1082
+ foreach ($result as $type => $data) {
1083
+ if (3 == $type) {
1084
+ $this->fallbackExitcode = (int) $data;
1085
+ } else {
1086
+ $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ /**
1092
+ * 捕获退出码
1093
+ */
1094
+ private function captureExitCode()
1095
+ {
1096
+ if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) {
1097
+ $this->exitcode = $this->processInformation['exitcode'];
1098
+ }
1099
+ }
1100
+
1101
+ /**
1102
+ * 关闭资源
1103
+ * @return int 退出码
1104
+ */
1105
+ private function close()
1106
+ {
1107
+ $this->processPipes->close();
1108
+ if (is_resource($this->process)) {
1109
+ $exitcode = proc_close($this->process);
1110
+ } else {
1111
+ $exitcode = -1;
1112
+ }
1113
+
1114
+ $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);
1115
+ $this->status = self::STATUS_TERMINATED;
1116
+
1117
+ if (-1 === $this->exitcode && null !== $this->fallbackExitcode) {
1118
+ $this->exitcode = $this->fallbackExitcode;
1119
+ } elseif (-1 === $this->exitcode && $this->processInformation['signaled']
1120
+ && 0 < $this->processInformation['termsig']
1121
+ ) {
1122
+ $this->exitcode = 128 + $this->processInformation['termsig'];
1123
+ }
1124
+
1125
+ return $this->exitcode;
1126
+ }
1127
+
1128
+ /**
1129
+ * 重置数据
1130
+ */
1131
+ private function resetProcessData()
1132
+ {
1133
+ $this->starttime = null;
1134
+ $this->callback = null;
1135
+ $this->exitcode = null;
1136
+ $this->fallbackExitcode = null;
1137
+ $this->processInformation = null;
1138
+ $this->stdout = null;
1139
+ $this->stderr = null;
1140
+ $this->process = null;
1141
+ $this->latestSignal = null;
1142
+ $this->status = self::STATUS_READY;
1143
+ $this->incrementalOutputOffset = 0;
1144
+ $this->incrementalErrorOutputOffset = 0;
1145
+ }
1146
+
1147
+ /**
1148
+ * 将一个 POSIX 信号发送到进程中。
1149
+ * @param int $signal
1150
+ * @param bool $throwException
1151
+ * @return bool
1152
+ */
1153
+ private function doSignal($signal, $throwException)
1154
+ {
1155
+ if (!$this->isRunning()) {
1156
+ if ($throwException) {
1157
+ throw new \LogicException('Can not send signal on a non running process.');
1158
+ }
1159
+
1160
+ return false;
1161
+ }
1162
+
1163
+ if ($this->isSigchildEnabled()) {
1164
+ if ($throwException) {
1165
+ throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
1166
+ }
1167
+
1168
+ return false;
1169
+ }
1170
+
1171
+ if (true !== @proc_terminate($this->process, $signal)) {
1172
+ if ($throwException) {
1173
+ throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1174
+ }
1175
+
1176
+ return false;
1177
+ }
1178
+
1179
+ $this->latestSignal = $signal;
1180
+
1181
+ return true;
1182
+ }
1183
+
1184
+ /**
1185
+ * 确保进程已经开启
1186
+ * @param string $functionName
1187
+ */
1188
+ private function requireProcessIsStarted($functionName)
1189
+ {
1190
+ if (!$this->isStarted()) {
1191
+ throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName));
1192
+ }
1193
+ }
1194
+
1195
+ /**
1196
+ * 确保进程已经终止
1197
+ * @param string $functionName
1198
+ */
1199
+ private function requireProcessIsTerminated($functionName)
1200
+ {
1201
+ if (!$this->isTerminated()) {
1202
+ throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
1203
+ }
1204
+ }
1205
+ }
thinkphp/library/think/Request.php ADDED
@@ -0,0 +1,1690 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Request
15
+ {
16
+ /**
17
+ * @var object 对象实例
18
+ */
19
+ protected static $instance;
20
+
21
+ protected $method;
22
+ /**
23
+ * @var string 域名(含协议和端口)
24
+ */
25
+ protected $domain;
26
+
27
+ /**
28
+ * @var string URL地址
29
+ */
30
+ protected $url;
31
+
32
+ /**
33
+ * @var string 基础URL
34
+ */
35
+ protected $baseUrl;
36
+
37
+ /**
38
+ * @var string 当前执行的文件
39
+ */
40
+ protected $baseFile;
41
+
42
+ /**
43
+ * @var string 访问的ROOT地址
44
+ */
45
+ protected $root;
46
+
47
+ /**
48
+ * @var string pathinfo
49
+ */
50
+ protected $pathinfo;
51
+
52
+ /**
53
+ * @var string pathinfo(不含后缀)
54
+ */
55
+ protected $path;
56
+
57
+ /**
58
+ * @var array 当前路由信息
59
+ */
60
+ protected $routeInfo = [];
61
+
62
+ /**
63
+ * @var array 环境变量
64
+ */
65
+ protected $env;
66
+
67
+ /**
68
+ * @var array 当前调度信息
69
+ */
70
+ protected $dispatch = [];
71
+ protected $module;
72
+ protected $controller;
73
+ protected $action;
74
+ // 当前语言集
75
+ protected $langset;
76
+
77
+ /**
78
+ * @var array 请求参数
79
+ */
80
+ protected $param = [];
81
+ protected $get = [];
82
+ protected $post = [];
83
+ protected $request = [];
84
+ protected $route = [];
85
+ protected $put;
86
+ protected $session = [];
87
+ protected $file = [];
88
+ protected $cookie = [];
89
+ protected $server = [];
90
+ protected $header = [];
91
+
92
+ /**
93
+ * @var array 资源类型
94
+ */
95
+ protected $mimeType = [
96
+ 'xml' => 'application/xml,text/xml,application/x-xml',
97
+ 'json' => 'application/json,text/x-json,application/jsonrequest,text/json',
98
+ 'js' => 'text/javascript,application/javascript,application/x-javascript',
99
+ 'css' => 'text/css',
100
+ 'rss' => 'application/rss+xml',
101
+ 'yaml' => 'application/x-yaml,text/yaml',
102
+ 'atom' => 'application/atom+xml',
103
+ 'pdf' => 'application/pdf',
104
+ 'text' => 'text/plain',
105
+ 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*',
106
+ 'csv' => 'text/csv',
107
+ 'html' => 'text/html,application/xhtml+xml,*/*',
108
+ ];
109
+
110
+ protected $content;
111
+
112
+ // 全局过滤规则
113
+ protected $filter;
114
+ // Hook扩展方法
115
+ protected static $hook = [];
116
+ // 绑定的属性
117
+ protected $bind = [];
118
+ // php://input
119
+ protected $input;
120
+ // 请求缓存
121
+ protected $cache;
122
+ // 缓存是否检查
123
+ protected $isCheckCache;
124
+ /**
125
+ * 是否合并Param
126
+ * @var bool
127
+ */
128
+ protected $mergeParam = false;
129
+
130
+ /**
131
+ * 构造函数
132
+ * @access protected
133
+ * @param array $options 参数
134
+ */
135
+ protected function __construct($options = [])
136
+ {
137
+ foreach ($options as $name => $item) {
138
+ if (property_exists($this, $name)) {
139
+ $this->$name = $item;
140
+ }
141
+ }
142
+ if (is_null($this->filter)) {
143
+ $this->filter = Config::get('default_filter');
144
+ }
145
+
146
+ // 保存 php://input
147
+ $this->input = file_get_contents('php://input');
148
+ }
149
+
150
+ public function __call($method, $args)
151
+ {
152
+ if (array_key_exists($method, self::$hook)) {
153
+ array_unshift($args, $this);
154
+ return call_user_func_array(self::$hook[$method], $args);
155
+ } else {
156
+ throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Hook 方法注入
162
+ * @access public
163
+ * @param string|array $method 方法名
164
+ * @param mixed $callback callable
165
+ * @return void
166
+ */
167
+ public static function hook($method, $callback = null)
168
+ {
169
+ if (is_array($method)) {
170
+ self::$hook = array_merge(self::$hook, $method);
171
+ } else {
172
+ self::$hook[$method] = $callback;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * 初始化
178
+ * @access public
179
+ * @param array $options 参数
180
+ * @return \think\Request
181
+ */
182
+ public static function instance($options = [])
183
+ {
184
+ if (is_null(self::$instance)) {
185
+ self::$instance = new static($options);
186
+ }
187
+ return self::$instance;
188
+ }
189
+
190
+ /**
191
+ * 销毁当前请求对象
192
+ * @access public
193
+ * @return void
194
+ */
195
+ public static function destroy()
196
+ {
197
+ if (!is_null(self::$instance)) {
198
+ self::$instance = null;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * 创建一个URL请求
204
+ * @access public
205
+ * @param string $uri URL地址
206
+ * @param string $method 请求类型
207
+ * @param array $params 请求参数
208
+ * @param array $cookie
209
+ * @param array $files
210
+ * @param array $server
211
+ * @param string $content
212
+ * @return \think\Request
213
+ */
214
+ public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null)
215
+ {
216
+ $server['PATH_INFO'] = '';
217
+ $server['REQUEST_METHOD'] = strtoupper($method);
218
+ $info = parse_url($uri);
219
+ if (isset($info['host'])) {
220
+ $server['SERVER_NAME'] = $info['host'];
221
+ $server['HTTP_HOST'] = $info['host'];
222
+ }
223
+ if (isset($info['scheme'])) {
224
+ if ('https' === $info['scheme']) {
225
+ $server['HTTPS'] = 'on';
226
+ $server['SERVER_PORT'] = 443;
227
+ } else {
228
+ unset($server['HTTPS']);
229
+ $server['SERVER_PORT'] = 80;
230
+ }
231
+ }
232
+ if (isset($info['port'])) {
233
+ $server['SERVER_PORT'] = $info['port'];
234
+ $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port'];
235
+ }
236
+ if (isset($info['user'])) {
237
+ $server['PHP_AUTH_USER'] = $info['user'];
238
+ }
239
+ if (isset($info['pass'])) {
240
+ $server['PHP_AUTH_PW'] = $info['pass'];
241
+ }
242
+ if (!isset($info['path'])) {
243
+ $info['path'] = '/';
244
+ }
245
+ $options = [];
246
+ $options[strtolower($method)] = $params;
247
+ $queryString = '';
248
+ if (isset($info['query'])) {
249
+ parse_str(html_entity_decode($info['query']), $query);
250
+ if (!empty($params)) {
251
+ $params = array_replace($query, $params);
252
+ $queryString = http_build_query($params, '', '&');
253
+ } else {
254
+ $params = $query;
255
+ $queryString = $info['query'];
256
+ }
257
+ } elseif (!empty($params)) {
258
+ $queryString = http_build_query($params, '', '&');
259
+ }
260
+ if ($queryString) {
261
+ parse_str($queryString, $get);
262
+ $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get;
263
+ }
264
+
265
+ $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : '');
266
+ $server['QUERY_STRING'] = $queryString;
267
+ $options['cookie'] = $cookie;
268
+ $options['param'] = $params;
269
+ $options['file'] = $files;
270
+ $options['server'] = $server;
271
+ $options['url'] = $server['REQUEST_URI'];
272
+ $options['baseUrl'] = $info['path'];
273
+ $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/');
274
+ $options['method'] = $server['REQUEST_METHOD'];
275
+ $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : '';
276
+ $options['content'] = $content;
277
+ self::$instance = new self($options);
278
+ return self::$instance;
279
+ }
280
+
281
+ /**
282
+ * 设置或获取当前包含协议的域名
283
+ * @access public
284
+ * @param string $domain 域名
285
+ * @return string
286
+ */
287
+ public function domain($domain = null)
288
+ {
289
+ if (!is_null($domain)) {
290
+ $this->domain = $domain;
291
+ return $this;
292
+ } elseif (!$this->domain) {
293
+ $this->domain = $this->scheme() . '://' . $this->host();
294
+ }
295
+ return $this->domain;
296
+ }
297
+
298
+ /**
299
+ * 设置或获取当前完整URL 包括QUERY_STRING
300
+ * @access public
301
+ * @param string|true $url URL地址 true 带域名获取
302
+ * @return string
303
+ */
304
+ public function url($url = null)
305
+ {
306
+ if (!is_null($url) && true !== $url) {
307
+ $this->url = $url;
308
+ return $this;
309
+ } elseif (!$this->url) {
310
+ if (IS_CLI) {
311
+ $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
312
+ } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
313
+ $this->url = $_SERVER['HTTP_X_REWRITE_URL'];
314
+ } elseif (isset($_SERVER['REQUEST_URI'])) {
315
+ $this->url = $_SERVER['REQUEST_URI'];
316
+ } elseif (isset($_SERVER['ORIG_PATH_INFO'])) {
317
+ $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
318
+ } else {
319
+ $this->url = '';
320
+ }
321
+ }
322
+ return true === $url ? $this->domain() . $this->url : $this->url;
323
+ }
324
+
325
+ /**
326
+ * 设置或获取当前URL 不含QUERY_STRING
327
+ * @access public
328
+ * @param string $url URL地址
329
+ * @return string
330
+ */
331
+ public function baseUrl($url = null)
332
+ {
333
+ if (!is_null($url) && true !== $url) {
334
+ $this->baseUrl = $url;
335
+ return $this;
336
+ } elseif (!$this->baseUrl) {
337
+ $str = $this->url();
338
+ $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str;
339
+ }
340
+ return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl;
341
+ }
342
+
343
+ /**
344
+ * 设置或获取当前执行的文件 SCRIPT_NAME
345
+ * @access public
346
+ * @param string $file 当前执行的文件
347
+ * @return string
348
+ */
349
+ public function baseFile($file = null)
350
+ {
351
+ if (!is_null($file) && true !== $file) {
352
+ $this->baseFile = $file;
353
+ return $this;
354
+ } elseif (!$this->baseFile) {
355
+ $url = '';
356
+ if (!IS_CLI) {
357
+ $script_name = basename($_SERVER['SCRIPT_FILENAME']);
358
+ if (basename($_SERVER['SCRIPT_NAME']) === $script_name) {
359
+ $url = $_SERVER['SCRIPT_NAME'];
360
+ } elseif (basename($_SERVER['PHP_SELF']) === $script_name) {
361
+ $url = $_SERVER['PHP_SELF'];
362
+ } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) {
363
+ $url = $_SERVER['ORIG_SCRIPT_NAME'];
364
+ } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) {
365
+ $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name;
366
+ } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) {
367
+ $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME']));
368
+ }
369
+ }
370
+ $this->baseFile = $url;
371
+ }
372
+ return true === $file ? $this->domain() . $this->baseFile : $this->baseFile;
373
+ }
374
+
375
+ /**
376
+ * 设置或获取URL访问根地址
377
+ * @access public
378
+ * @param string $url URL地址
379
+ * @return string
380
+ */
381
+ public function root($url = null)
382
+ {
383
+ if (!is_null($url) && true !== $url) {
384
+ $this->root = $url;
385
+ return $this;
386
+ } elseif (!$this->root) {
387
+ $file = $this->baseFile();
388
+ if ($file && 0 !== strpos($this->url(), $file)) {
389
+ $file = str_replace('\\', '/', dirname($file));
390
+ }
391
+ $this->root = rtrim($file, '/');
392
+ }
393
+ return true === $url ? $this->domain() . $this->root : $this->root;
394
+ }
395
+
396
+ /**
397
+ * 获取当前请求URL的pathinfo信息(含URL后缀)
398
+ * @access public
399
+ * @return string
400
+ */
401
+ public function pathinfo()
402
+ {
403
+ if (is_null($this->pathinfo)) {
404
+ if (isset($_GET[Config::get('var_pathinfo')])) {
405
+ // 判断URL里面是否有兼容模式参数
406
+ $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
407
+ unset($_GET[Config::get('var_pathinfo')]);
408
+ } elseif (IS_CLI) {
409
+ // CLI模式下 index.php module/controller/action/params/...
410
+ $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
411
+ }
412
+
413
+ // 分析PATHINFO信息
414
+ if (!isset($_SERVER['PATH_INFO'])) {
415
+ foreach (Config::get('pathinfo_fetch') as $type) {
416
+ if (!empty($_SERVER[$type])) {
417
+ $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
418
+ substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
419
+ break;
420
+ }
421
+ }
422
+ }
423
+ $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
424
+ }
425
+ return $this->pathinfo;
426
+ }
427
+
428
+ /**
429
+ * 获取当前请求URL的pathinfo信息(不含URL后缀)
430
+ * @access public
431
+ * @return string
432
+ */
433
+ public function path()
434
+ {
435
+ if (is_null($this->path)) {
436
+ $suffix = Config::get('url_html_suffix');
437
+ $pathinfo = $this->pathinfo();
438
+ if (false === $suffix) {
439
+ // 禁止伪静态访问
440
+ $this->path = $pathinfo;
441
+ } elseif ($suffix) {
442
+ // 去除正常的URL后缀
443
+ $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
444
+ } else {
445
+ // 允许任何后缀访问
446
+ $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
447
+ }
448
+ }
449
+ return $this->path;
450
+ }
451
+
452
+ /**
453
+ * 当前URL的访问后缀
454
+ * @access public
455
+ * @return string
456
+ */
457
+ public function ext()
458
+ {
459
+ return pathinfo($this->pathinfo(), PATHINFO_EXTENSION);
460
+ }
461
+
462
+ /**
463
+ * 获取当前请求的时间
464
+ * @access public
465
+ * @param bool $float 是否使用浮点类型
466
+ * @return integer|float
467
+ */
468
+ public function time($float = false)
469
+ {
470
+ return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME'];
471
+ }
472
+
473
+ /**
474
+ * 当前请求的资源类型
475
+ * @access public
476
+ * @return false|string
477
+ */
478
+ public function type()
479
+ {
480
+ $accept = $this->server('HTTP_ACCEPT');
481
+ if (empty($accept)) {
482
+ return false;
483
+ }
484
+
485
+ foreach ($this->mimeType as $key => $val) {
486
+ $array = explode(',', $val);
487
+ foreach ($array as $k => $v) {
488
+ if (stristr($accept, $v)) {
489
+ return $key;
490
+ }
491
+ }
492
+ }
493
+ return false;
494
+ }
495
+
496
+ /**
497
+ * 设置资源类型
498
+ * @access public
499
+ * @param string|array $type 资源类型名
500
+ * @param string $val 资源类型
501
+ * @return void
502
+ */
503
+ public function mimeType($type, $val = '')
504
+ {
505
+ if (is_array($type)) {
506
+ $this->mimeType = array_merge($this->mimeType, $type);
507
+ } else {
508
+ $this->mimeType[$type] = $val;
509
+ }
510
+ }
511
+
512
+ /**
513
+ * 当前的请求类型
514
+ * @access public
515
+ * @param bool $method true 获取原始请求类型
516
+ * @return string
517
+ */
518
+ public function method($method = false)
519
+ {
520
+ if (true === $method) {
521
+ // 获取原始请求类型
522
+ return $this->server('REQUEST_METHOD') ?: 'GET';
523
+ } elseif (!$this->method) {
524
+ if (isset($_POST[Config::get('var_method')])) {
525
+ $method = strtoupper($_POST[Config::get('var_method')]);
526
+ if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) {
527
+ $this->method = $method;
528
+ $this->{$this->method}($_POST);
529
+ } else {
530
+ $this->method = 'POST';
531
+ }
532
+ unset($_POST[Config::get('var_method')]);
533
+ } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
534
+ $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
535
+ } else {
536
+ $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
537
+ }
538
+ }
539
+ return $this->method;
540
+ }
541
+
542
+ /**
543
+ * 是否为GET请求
544
+ * @access public
545
+ * @return bool
546
+ */
547
+ public function isGet()
548
+ {
549
+ return $this->method() == 'GET';
550
+ }
551
+
552
+ /**
553
+ * 是否为POST请求
554
+ * @access public
555
+ * @return bool
556
+ */
557
+ public function isPost()
558
+ {
559
+ return $this->method() == 'POST';
560
+ }
561
+
562
+ /**
563
+ * 是否为PUT请求
564
+ * @access public
565
+ * @return bool
566
+ */
567
+ public function isPut()
568
+ {
569
+ return $this->method() == 'PUT';
570
+ }
571
+
572
+ /**
573
+ * 是否为DELTE请求
574
+ * @access public
575
+ * @return bool
576
+ */
577
+ public function isDelete()
578
+ {
579
+ return $this->method() == 'DELETE';
580
+ }
581
+
582
+ /**
583
+ * 是否为HEAD请求
584
+ * @access public
585
+ * @return bool
586
+ */
587
+ public function isHead()
588
+ {
589
+ return $this->method() == 'HEAD';
590
+ }
591
+
592
+ /**
593
+ * 是否为PATCH请求
594
+ * @access public
595
+ * @return bool
596
+ */
597
+ public function isPatch()
598
+ {
599
+ return $this->method() == 'PATCH';
600
+ }
601
+
602
+ /**
603
+ * 是否为OPTIONS请求
604
+ * @access public
605
+ * @return bool
606
+ */
607
+ public function isOptions()
608
+ {
609
+ return $this->method() == 'OPTIONS';
610
+ }
611
+
612
+ /**
613
+ * 是否为cli
614
+ * @access public
615
+ * @return bool
616
+ */
617
+ public function isCli()
618
+ {
619
+ return PHP_SAPI == 'cli';
620
+ }
621
+
622
+ /**
623
+ * 是否为cgi
624
+ * @access public
625
+ * @return bool
626
+ */
627
+ public function isCgi()
628
+ {
629
+ return strpos(PHP_SAPI, 'cgi') === 0;
630
+ }
631
+
632
+ /**
633
+ * 获取当前请求的参数
634
+ * @access public
635
+ * @param string|array $name 变量名
636
+ * @param mixed $default 默认值
637
+ * @param string|array $filter 过滤方法
638
+ * @return mixed
639
+ */
640
+ public function param($name = '', $default = null, $filter = '')
641
+ {
642
+ if (empty($this->mergeParam)) {
643
+ $method = $this->method(true);
644
+ // 自动获取请求变量
645
+ switch ($method) {
646
+ case 'POST':
647
+ $vars = $this->post(false);
648
+ break;
649
+ case 'PUT':
650
+ case 'DELETE':
651
+ case 'PATCH':
652
+ $vars = $this->put(false);
653
+ break;
654
+ default:
655
+ $vars = [];
656
+ }
657
+ // 当前请求参数和URL地址中的参数合并
658
+ $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
659
+ $this->mergeParam = true;
660
+ }
661
+ if (true === $name) {
662
+ // 获取包含文件上传信息的数组
663
+ $file = $this->file();
664
+ $data = is_array($file) ? array_merge($this->param, $file) : $this->param;
665
+ return $this->input($data, '', $default, $filter);
666
+ }
667
+ return $this->input($this->param, $name, $default, $filter);
668
+ }
669
+
670
+ /**
671
+ * 设置获取路由参数
672
+ * @access public
673
+ * @param string|array $name 变量名
674
+ * @param mixed $default 默认值
675
+ * @param string|array $filter 过滤方法
676
+ * @return mixed
677
+ */
678
+ public function route($name = '', $default = null, $filter = '')
679
+ {
680
+ if (is_array($name)) {
681
+ $this->param = [];
682
+ $this->mergeParam = false;
683
+ return $this->route = array_merge($this->route, $name);
684
+ }
685
+ return $this->input($this->route, $name, $default, $filter);
686
+ }
687
+
688
+ /**
689
+ * 设置获取GET参数
690
+ * @access public
691
+ * @param string|array $name 变量名
692
+ * @param mixed $default 默认值
693
+ * @param string|array $filter 过滤方法
694
+ * @return mixed
695
+ */
696
+ public function get($name = '', $default = null, $filter = '')
697
+ {
698
+ if (empty($this->get)) {
699
+ $this->get = $_GET;
700
+ }
701
+ if (is_array($name)) {
702
+ $this->param = [];
703
+ $this->mergeParam = false;
704
+ return $this->get = array_merge($this->get, $name);
705
+ }
706
+ return $this->input($this->get, $name, $default, $filter);
707
+ }
708
+
709
+ /**
710
+ * 设置获取POST参数
711
+ * @access public
712
+ * @param string $name 变量名
713
+ * @param mixed $default 默认值
714
+ * @param string|array $filter 过滤方法
715
+ * @return mixed
716
+ */
717
+ public function post($name = '', $default = null, $filter = '')
718
+ {
719
+ if (empty($this->post)) {
720
+ $content = $this->input;
721
+ if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) {
722
+ $this->post = (array) json_decode($content, true);
723
+ } else {
724
+ $this->post = $_POST;
725
+ }
726
+ }
727
+ if (is_array($name)) {
728
+ $this->param = [];
729
+ $this->mergeParam = false;
730
+ return $this->post = array_merge($this->post, $name);
731
+ }
732
+ return $this->input($this->post, $name, $default, $filter);
733
+ }
734
+
735
+ /**
736
+ * 设置获取PUT参数
737
+ * @access public
738
+ * @param string|array $name 变量名
739
+ * @param mixed $default 默认值
740
+ * @param string|array $filter 过滤方法
741
+ * @return mixed
742
+ */
743
+ public function put($name = '', $default = null, $filter = '')
744
+ {
745
+ if (is_null($this->put)) {
746
+ $content = $this->input;
747
+ if (false !== strpos($this->contentType(), 'application/json')) {
748
+ $this->put = (array) json_decode($content, true);
749
+ } else {
750
+ parse_str($content, $this->put);
751
+ }
752
+ }
753
+ if (is_array($name)) {
754
+ $this->param = [];
755
+ $this->mergeParam = false;
756
+ return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name);
757
+ }
758
+
759
+ return $this->input($this->put, $name, $default, $filter);
760
+ }
761
+
762
+ /**
763
+ * 设置获取DELETE参数
764
+ * @access public
765
+ * @param string|array $name 变量名
766
+ * @param mixed $default 默认值
767
+ * @param string|array $filter 过滤方法
768
+ * @return mixed
769
+ */
770
+ public function delete($name = '', $default = null, $filter = '')
771
+ {
772
+ return $this->put($name, $default, $filter);
773
+ }
774
+
775
+ /**
776
+ * 设置获取PATCH参数
777
+ * @access public
778
+ * @param string|array $name 变量名
779
+ * @param mixed $default 默认值
780
+ * @param string|array $filter 过滤方法
781
+ * @return mixed
782
+ */
783
+ public function patch($name = '', $default = null, $filter = '')
784
+ {
785
+ return $this->put($name, $default, $filter);
786
+ }
787
+
788
+ /**
789
+ * 获取request变量
790
+ * @param string $name 数据名称
791
+ * @param string $default 默认值
792
+ * @param string|array $filter 过滤方法
793
+ * @return mixed
794
+ */
795
+ public function request($name = '', $default = null, $filter = '')
796
+ {
797
+ if (empty($this->request)) {
798
+ $this->request = $_REQUEST;
799
+ }
800
+ if (is_array($name)) {
801
+ $this->param = [];
802
+ $this->mergeParam = false;
803
+ return $this->request = array_merge($this->request, $name);
804
+ }
805
+ return $this->input($this->request, $name, $default, $filter);
806
+ }
807
+
808
+ /**
809
+ * 获取session数据
810
+ * @access public
811
+ * @param string|array $name 数据名称
812
+ * @param string $default 默认值
813
+ * @param string|array $filter 过滤方法
814
+ * @return mixed
815
+ */
816
+ public function session($name = '', $default = null, $filter = '')
817
+ {
818
+ if (empty($this->session)) {
819
+ $this->session = Session::get();
820
+ }
821
+ if (is_array($name)) {
822
+ return $this->session = array_merge($this->session, $name);
823
+ }
824
+ return $this->input($this->session, $name, $default, $filter);
825
+ }
826
+
827
+ /**
828
+ * 获取cookie参数
829
+ * @access public
830
+ * @param string|array $name 数据名称
831
+ * @param string $default 默认值
832
+ * @param string|array $filter 过滤方法
833
+ * @return mixed
834
+ */
835
+ public function cookie($name = '', $default = null, $filter = '')
836
+ {
837
+ if (empty($this->cookie)) {
838
+ $this->cookie = Cookie::get();
839
+ }
840
+ if (is_array($name)) {
841
+ return $this->cookie = array_merge($this->cookie, $name);
842
+ } elseif (!empty($name)) {
843
+ $data = Cookie::has($name) ? Cookie::get($name) : $default;
844
+ } else {
845
+ $data = $this->cookie;
846
+ }
847
+
848
+ // 解析过滤器
849
+ $filter = $this->getFilter($filter, $default);
850
+
851
+ if (is_array($data)) {
852
+ array_walk_recursive($data, [$this, 'filterValue'], $filter);
853
+ reset($data);
854
+ } else {
855
+ $this->filterValue($data, $name, $filter);
856
+ }
857
+ return $data;
858
+ }
859
+
860
+ /**
861
+ * 获取server参数
862
+ * @access public
863
+ * @param string|array $name 数据名称
864
+ * @param string $default 默认值
865
+ * @param string|array $filter 过滤方法
866
+ * @return mixed
867
+ */
868
+ public function server($name = '', $default = null, $filter = '')
869
+ {
870
+ if (empty($this->server)) {
871
+ $this->server = $_SERVER;
872
+ }
873
+ if (is_array($name)) {
874
+ return $this->server = array_merge($this->server, $name);
875
+ }
876
+ return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter);
877
+ }
878
+
879
+ /**
880
+ * 获取上传的文件信息
881
+ * @access public
882
+ * @param string|array $name 名称
883
+ * @return null|array|\think\File
884
+ */
885
+ public function file($name = '')
886
+ {
887
+ if (empty($this->file)) {
888
+ $this->file = isset($_FILES) ? $_FILES : [];
889
+ }
890
+ if (is_array($name)) {
891
+ return $this->file = array_merge($this->file, $name);
892
+ }
893
+ $files = $this->file;
894
+ if (!empty($files)) {
895
+ // 处理上传文件
896
+ $array = [];
897
+ foreach ($files as $key => $file) {
898
+ if (is_array($file['name'])) {
899
+ $item = [];
900
+ $keys = array_keys($file);
901
+ $count = count($file['name']);
902
+ for ($i = 0; $i < $count; $i++) {
903
+ if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) {
904
+ continue;
905
+ }
906
+ $temp['key'] = $key;
907
+ foreach ($keys as $_key) {
908
+ $temp[$_key] = $file[$_key][$i];
909
+ }
910
+ $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp);
911
+ }
912
+ $array[$key] = $item;
913
+ } else {
914
+ if ($file instanceof File) {
915
+ $array[$key] = $file;
916
+ } else {
917
+ if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) {
918
+ continue;
919
+ }
920
+ $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file);
921
+ }
922
+ }
923
+ }
924
+ if (strpos($name, '.')) {
925
+ list($name, $sub) = explode('.', $name);
926
+ }
927
+ if ('' === $name) {
928
+ // 获取全部文件
929
+ return $array;
930
+ } elseif (isset($sub) && isset($array[$name][$sub])) {
931
+ return $array[$name][$sub];
932
+ } elseif (isset($array[$name])) {
933
+ return $array[$name];
934
+ }
935
+ }
936
+ return;
937
+ }
938
+
939
+ /**
940
+ * 获取环境变量
941
+ * @param string|array $name 数据名称
942
+ * @param string $default 默认值
943
+ * @param string|array $filter 过滤方法
944
+ * @return mixed
945
+ */
946
+ public function env($name = '', $default = null, $filter = '')
947
+ {
948
+ if (empty($this->env)) {
949
+ $this->env = $_ENV;
950
+ }
951
+ if (is_array($name)) {
952
+ return $this->env = array_merge($this->env, $name);
953
+ }
954
+ return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter);
955
+ }
956
+
957
+ /**
958
+ * 设置或者获取当前的Header
959
+ * @access public
960
+ * @param string|array $name header名称
961
+ * @param string $default 默认值
962
+ * @return string
963
+ */
964
+ public function header($name = '', $default = null)
965
+ {
966
+ if (empty($this->header)) {
967
+ $header = [];
968
+ if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
969
+ $header = $result;
970
+ } else {
971
+ $server = $this->server ?: $_SERVER;
972
+ foreach ($server as $key => $val) {
973
+ if (0 === strpos($key, 'HTTP_')) {
974
+ $key = str_replace('_', '-', strtolower(substr($key, 5)));
975
+ $header[$key] = $val;
976
+ }
977
+ }
978
+ if (isset($server['CONTENT_TYPE'])) {
979
+ $header['content-type'] = $server['CONTENT_TYPE'];
980
+ }
981
+ if (isset($server['CONTENT_LENGTH'])) {
982
+ $header['content-length'] = $server['CONTENT_LENGTH'];
983
+ }
984
+ }
985
+ $this->header = array_change_key_case($header);
986
+ }
987
+ if (is_array($name)) {
988
+ return $this->header = array_merge($this->header, $name);
989
+ }
990
+ if ('' === $name) {
991
+ return $this->header;
992
+ }
993
+ $name = str_replace('_', '-', strtolower($name));
994
+ return isset($this->header[$name]) ? $this->header[$name] : $default;
995
+ }
996
+
997
+ /**
998
+ * 获取变量 支持过滤和默认值
999
+ * @param array $data 数据源
1000
+ * @param string|false $name 字段名
1001
+ * @param mixed $default 默认值
1002
+ * @param string|array $filter 过滤函数
1003
+ * @return mixed
1004
+ */
1005
+ public function input($data = [], $name = '', $default = null, $filter = '')
1006
+ {
1007
+ if (false === $name) {
1008
+ // 获取原始数据
1009
+ return $data;
1010
+ }
1011
+ $name = (string) $name;
1012
+ if ('' != $name) {
1013
+ // 解析name
1014
+ if (strpos($name, '/')) {
1015
+ list($name, $type) = explode('/', $name);
1016
+ } else {
1017
+ $type = 's';
1018
+ }
1019
+ // 按.拆分成多维数组进行判断
1020
+ foreach (explode('.', $name) as $val) {
1021
+ if (isset($data[$val])) {
1022
+ $data = $data[$val];
1023
+ } else {
1024
+ // 无输入数据,返回默认值
1025
+ return $default;
1026
+ }
1027
+ }
1028
+ if (is_object($data)) {
1029
+ return $data;
1030
+ }
1031
+ }
1032
+
1033
+ // 解析过滤器
1034
+ $filter = $this->getFilter($filter, $default);
1035
+
1036
+ if (is_array($data)) {
1037
+ array_walk_recursive($data, [$this, 'filterValue'], $filter);
1038
+ reset($data);
1039
+ } else {
1040
+ $this->filterValue($data, $name, $filter);
1041
+ }
1042
+
1043
+ if (isset($type) && $data !== $default) {
1044
+ // 强制类型转换
1045
+ $this->typeCast($data, $type);
1046
+ }
1047
+ return $data;
1048
+ }
1049
+
1050
+ /**
1051
+ * 设置或获取当前的过滤规则
1052
+ * @param mixed $filter 过滤规则
1053
+ * @return mixed
1054
+ */
1055
+ public function filter($filter = null)
1056
+ {
1057
+ if (is_null($filter)) {
1058
+ return $this->filter;
1059
+ } else {
1060
+ $this->filter = $filter;
1061
+ }
1062
+ }
1063
+
1064
+ protected function getFilter($filter, $default)
1065
+ {
1066
+ if (is_null($filter)) {
1067
+ $filter = [];
1068
+ } else {
1069
+ $filter = $filter ?: $this->filter;
1070
+ if (is_string($filter) && false === strpos($filter, '/')) {
1071
+ $filter = explode(',', $filter);
1072
+ } else {
1073
+ $filter = (array) $filter;
1074
+ }
1075
+ }
1076
+
1077
+ $filter[] = $default;
1078
+ return $filter;
1079
+ }
1080
+
1081
+ /**
1082
+ * 递归过滤给定的值
1083
+ * @param mixed $value 键值
1084
+ * @param mixed $key 键名
1085
+ * @param array $filters 过滤方法+默认值
1086
+ * @return mixed
1087
+ */
1088
+ private function filterValue(&$value, $key, $filters)
1089
+ {
1090
+ $default = array_pop($filters);
1091
+ foreach ($filters as $filter) {
1092
+ if (is_callable($filter)) {
1093
+ // 调用函数或者方法过滤
1094
+ $value = call_user_func($filter, $value);
1095
+ } elseif (is_scalar($value)) {
1096
+ if (false !== strpos($filter, '/')) {
1097
+ // 正则过滤
1098
+ if (!preg_match($filter, $value)) {
1099
+ // 匹配不成功返回默认值
1100
+ $value = $default;
1101
+ break;
1102
+ }
1103
+ } elseif (!empty($filter)) {
1104
+ // filter函数不存在时, 则使用filter_var进行过滤
1105
+ // filter为非整形值时, 调用filter_id取得过滤id
1106
+ $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
1107
+ if (false === $value) {
1108
+ $value = $default;
1109
+ break;
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ return $this->filterExp($value);
1115
+ }
1116
+
1117
+ /**
1118
+ * 过滤表单中的表达式
1119
+ * @param string $value
1120
+ * @return void
1121
+ */
1122
+ public function filterExp(&$value)
1123
+ {
1124
+ // 过滤查询特殊字符
1125
+ if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) {
1126
+ $value .= ' ';
1127
+ }
1128
+ // TODO 其他安全过滤
1129
+ }
1130
+
1131
+ /**
1132
+ * 强制类型转换
1133
+ * @param string $data
1134
+ * @param string $type
1135
+ * @return mixed
1136
+ */
1137
+ private function typeCast(&$data, $type)
1138
+ {
1139
+ switch (strtolower($type)) {
1140
+ // 数组
1141
+ case 'a':
1142
+ $data = (array) $data;
1143
+ break;
1144
+ // 数字
1145
+ case 'd':
1146
+ $data = (int) $data;
1147
+ break;
1148
+ // 浮点
1149
+ case 'f':
1150
+ $data = (float) $data;
1151
+ break;
1152
+ // 布尔
1153
+ case 'b':
1154
+ $data = (boolean) $data;
1155
+ break;
1156
+ // 字符串
1157
+ case 's':
1158
+ default:
1159
+ if (is_scalar($data)) {
1160
+ $data = (string) $data;
1161
+ } else {
1162
+ throw new \InvalidArgumentException('variable type error:' . gettype($data));
1163
+ }
1164
+ }
1165
+ }
1166
+
1167
+ /**
1168
+ * 是否存在某个请求参数
1169
+ * @access public
1170
+ * @param string $name 变量名
1171
+ * @param string $type 变量类型
1172
+ * @param bool $checkEmpty 是否检测空值
1173
+ * @return mixed
1174
+ */
1175
+ public function has($name, $type = 'param', $checkEmpty = false)
1176
+ {
1177
+ if (empty($this->$type)) {
1178
+ $param = $this->$type();
1179
+ } else {
1180
+ $param = $this->$type;
1181
+ }
1182
+ // 按.拆分成多维数组进行判断
1183
+ foreach (explode('.', $name) as $val) {
1184
+ if (isset($param[$val])) {
1185
+ $param = $param[$val];
1186
+ } else {
1187
+ return false;
1188
+ }
1189
+ }
1190
+ return ($checkEmpty && '' === $param) ? false : true;
1191
+ }
1192
+
1193
+ /**
1194
+ * 获取指定的参数
1195
+ * @access public
1196
+ * @param string|array $name 变量名
1197
+ * @param string $type 变量类型
1198
+ * @return mixed
1199
+ */
1200
+ public function only($name, $type = 'param')
1201
+ {
1202
+ $param = $this->$type();
1203
+ if (is_string($name)) {
1204
+ $name = explode(',', $name);
1205
+ }
1206
+ $item = [];
1207
+ foreach ($name as $key) {
1208
+ if (isset($param[$key])) {
1209
+ $item[$key] = $param[$key];
1210
+ }
1211
+ }
1212
+ return $item;
1213
+ }
1214
+
1215
+ /**
1216
+ * 排除指定参数获取
1217
+ * @access public
1218
+ * @param string|array $name 变量名
1219
+ * @param string $type 变量类型
1220
+ * @return mixed
1221
+ */
1222
+ public function except($name, $type = 'param')
1223
+ {
1224
+ $param = $this->$type();
1225
+ if (is_string($name)) {
1226
+ $name = explode(',', $name);
1227
+ }
1228
+ foreach ($name as $key) {
1229
+ if (isset($param[$key])) {
1230
+ unset($param[$key]);
1231
+ }
1232
+ }
1233
+ return $param;
1234
+ }
1235
+
1236
+ /**
1237
+ * 当前是否ssl
1238
+ * @access public
1239
+ * @return bool
1240
+ */
1241
+ public function isSsl()
1242
+ {
1243
+ $server = array_merge($_SERVER, $this->server);
1244
+ if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) {
1245
+ return true;
1246
+ } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) {
1247
+ return true;
1248
+ } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) {
1249
+ return true;
1250
+ } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) {
1251
+ return true;
1252
+ } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) {
1253
+ return true;
1254
+ }
1255
+ return false;
1256
+ }
1257
+
1258
+ /**
1259
+ * 当前是否Ajax请求
1260
+ * @access public
1261
+ * @param bool $ajax true 获取原始ajax请求
1262
+ * @return bool
1263
+ */
1264
+ public function isAjax($ajax = false)
1265
+ {
1266
+ $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower');
1267
+ $result = ('xmlhttprequest' == $value) ? true : false;
1268
+ if (true === $ajax) {
1269
+ return $result;
1270
+ } else {
1271
+ $result = $this->param(Config::get('var_ajax')) ? true : $result;
1272
+ $this->mergeParam = false;
1273
+ return $result;
1274
+ }
1275
+ }
1276
+
1277
+ /**
1278
+ * 当前是否Pjax请求
1279
+ * @access public
1280
+ * @param bool $pjax true 获取原始pjax请求
1281
+ * @return bool
1282
+ */
1283
+ public function isPjax($pjax = false)
1284
+ {
1285
+ $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false;
1286
+ if (true === $pjax) {
1287
+ return $result;
1288
+ } else {
1289
+ $result = $this->param(Config::get('var_pjax')) ? true : $result;
1290
+ $this->mergeParam = false;
1291
+ return $result;
1292
+ }
1293
+ }
1294
+
1295
+ /**
1296
+ * 获取客户端IP地址
1297
+ * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
1298
+ * @param boolean $adv 是否进行高级模式获取(有可能被伪装)
1299
+ * @return mixed
1300
+ */
1301
+ public function ip($type = 0, $adv = true)
1302
+ {
1303
+ $type = $type ? 1 : 0;
1304
+ static $ip = null;
1305
+ if (null !== $ip) {
1306
+ return $ip[$type];
1307
+ }
1308
+
1309
+ $httpAgentIp = Config::get('http_agent_ip');
1310
+
1311
+ if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) {
1312
+ $ip = $_SERVER[$httpAgentIp];
1313
+ } elseif ($adv) {
1314
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1315
+ $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
1316
+ $pos = array_search('unknown', $arr);
1317
+ if (false !== $pos) {
1318
+ unset($arr[$pos]);
1319
+ }
1320
+ $ip = trim(current($arr));
1321
+ } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
1322
+ $ip = $_SERVER['HTTP_CLIENT_IP'];
1323
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
1324
+ $ip = $_SERVER['REMOTE_ADDR'];
1325
+ }
1326
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
1327
+ $ip = $_SERVER['REMOTE_ADDR'];
1328
+ }
1329
+ // IP地址合法验证
1330
+ $long = sprintf("%u", ip2long($ip));
1331
+ $ip = $long ? [$ip, $long] : ['0.0.0.0', 0];
1332
+ return $ip[$type];
1333
+ }
1334
+
1335
+ /**
1336
+ * 检测是否使用手机访问
1337
+ * @access public
1338
+ * @return bool
1339
+ */
1340
+ public function isMobile()
1341
+ {
1342
+ if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) {
1343
+ return true;
1344
+ } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) {
1345
+ return true;
1346
+ } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) {
1347
+ return true;
1348
+ } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) {
1349
+ return true;
1350
+ } else {
1351
+ return false;
1352
+ }
1353
+ }
1354
+
1355
+ /**
1356
+ * 当前URL地址中的scheme参数
1357
+ * @access public
1358
+ * @return string
1359
+ */
1360
+ public function scheme()
1361
+ {
1362
+ return $this->isSsl() ? 'https' : 'http';
1363
+ }
1364
+
1365
+ /**
1366
+ * 当前请求URL地址中的query参数
1367
+ * @access public
1368
+ * @return string
1369
+ */
1370
+ public function query()
1371
+ {
1372
+ return $this->server('QUERY_STRING');
1373
+ }
1374
+
1375
+ /**
1376
+ * 当前请求的host
1377
+ * @access public
1378
+ * @param bool $strict true 仅仅获取HOST
1379
+ * @return string
1380
+ */
1381
+ public function host($strict = false)
1382
+ {
1383
+ if (isset($_SERVER['HTTP_X_REAL_HOST'])) {
1384
+ $host = $_SERVER['HTTP_X_REAL_HOST'];
1385
+ } else {
1386
+ $host = $this->server('HTTP_HOST');
1387
+ }
1388
+
1389
+ return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
1390
+ }
1391
+
1392
+ /**
1393
+ * 当前请求URL地址中的port参数
1394
+ * @access public
1395
+ * @return integer
1396
+ */
1397
+ public function port()
1398
+ {
1399
+ return $this->server('SERVER_PORT');
1400
+ }
1401
+
1402
+ /**
1403
+ * 当前请求 SERVER_PROTOCOL
1404
+ * @access public
1405
+ * @return integer
1406
+ */
1407
+ public function protocol()
1408
+ {
1409
+ return $this->server('SERVER_PROTOCOL');
1410
+ }
1411
+
1412
+ /**
1413
+ * 当前请求 REMOTE_PORT
1414
+ * @access public
1415
+ * @return integer
1416
+ */
1417
+ public function remotePort()
1418
+ {
1419
+ return $this->server('REMOTE_PORT');
1420
+ }
1421
+
1422
+ /**
1423
+ * 当前请求 HTTP_CONTENT_TYPE
1424
+ * @access public
1425
+ * @return string
1426
+ */
1427
+ public function contentType()
1428
+ {
1429
+ $contentType = $this->server('CONTENT_TYPE');
1430
+ if ($contentType) {
1431
+ if (strpos($contentType, ';')) {
1432
+ list($type) = explode(';', $contentType);
1433
+ } else {
1434
+ $type = $contentType;
1435
+ }
1436
+ return trim($type);
1437
+ }
1438
+ return '';
1439
+ }
1440
+
1441
+ /**
1442
+ * 获取当前请求的路由信息
1443
+ * @access public
1444
+ * @param array $route 路由名称
1445
+ * @return array
1446
+ */
1447
+ public function routeInfo($route = [])
1448
+ {
1449
+ if (!empty($route)) {
1450
+ $this->routeInfo = $route;
1451
+ } else {
1452
+ return $this->routeInfo;
1453
+ }
1454
+ }
1455
+
1456
+ /**
1457
+ * 设置或者获取当前请求的调度信息
1458
+ * @access public
1459
+ * @param array $dispatch 调度信息
1460
+ * @return array
1461
+ */
1462
+ public function dispatch($dispatch = null)
1463
+ {
1464
+ if (!is_null($dispatch)) {
1465
+ $this->dispatch = $dispatch;
1466
+ }
1467
+ return $this->dispatch;
1468
+ }
1469
+
1470
+ /**
1471
+ * 设置或者获取当前的模块名
1472
+ * @access public
1473
+ * @param string $module 模块名
1474
+ * @return string|Request
1475
+ */
1476
+ public function module($module = null)
1477
+ {
1478
+ if (!is_null($module)) {
1479
+ $this->module = $module;
1480
+ return $this;
1481
+ } else {
1482
+ return $this->module ?: '';
1483
+ }
1484
+ }
1485
+
1486
+ /**
1487
+ * 设置或者获取当前的控制器名
1488
+ * @access public
1489
+ * @param string $controller 控制器名
1490
+ * @return string|Request
1491
+ */
1492
+ public function controller($controller = null)
1493
+ {
1494
+ if (!is_null($controller)) {
1495
+ $this->controller = $controller;
1496
+ return $this;
1497
+ } else {
1498
+ return $this->controller ?: '';
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * 设置或者获取当前的操作名
1504
+ * @access public
1505
+ * @param string $action 操作名
1506
+ * @return string|Request
1507
+ */
1508
+ public function action($action = null)
1509
+ {
1510
+ if (!is_null($action) && !is_bool($action)) {
1511
+ $this->action = $action;
1512
+ return $this;
1513
+ } else {
1514
+ $name = $this->action ?: '';
1515
+ return true === $action ? $name : strtolower($name);
1516
+ }
1517
+ }
1518
+
1519
+ /**
1520
+ * 设置或者获取当前的语言
1521
+ * @access public
1522
+ * @param string $lang 语言名
1523
+ * @return string|Request
1524
+ */
1525
+ public function langset($lang = null)
1526
+ {
1527
+ if (!is_null($lang)) {
1528
+ $this->langset = $lang;
1529
+ return $this;
1530
+ } else {
1531
+ return $this->langset ?: '';
1532
+ }
1533
+ }
1534
+
1535
+ /**
1536
+ * 设置或者获取当前请求的content
1537
+ * @access public
1538
+ * @return string
1539
+ */
1540
+ public function getContent()
1541
+ {
1542
+ if (is_null($this->content)) {
1543
+ $this->content = $this->input;
1544
+ }
1545
+ return $this->content;
1546
+ }
1547
+
1548
+ /**
1549
+ * 获取当前请求的php://input
1550
+ * @access public
1551
+ * @return string
1552
+ */
1553
+ public function getInput()
1554
+ {
1555
+ return $this->input;
1556
+ }
1557
+
1558
+ /**
1559
+ * 生成请求令牌
1560
+ * @access public
1561
+ * @param string $name 令牌名称
1562
+ * @param mixed $type 令牌生成方法
1563
+ * @return string
1564
+ */
1565
+ public function token($name = '__token__', $type = 'md5')
1566
+ {
1567
+ $type = is_callable($type) ? $type : 'md5';
1568
+ $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']);
1569
+ if ($this->isAjax()) {
1570
+ header($name . ': ' . $token);
1571
+ }
1572
+ Session::set($name, $token);
1573
+ return $token;
1574
+ }
1575
+
1576
+ /**
1577
+ * 设置当前地址的请求缓存
1578
+ * @access public
1579
+ * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id
1580
+ * @param mixed $expire 缓存有效期
1581
+ * @param array $except 缓存排除
1582
+ * @param string $tag 缓存标签
1583
+ * @return void
1584
+ */
1585
+ public function cache($key, $expire = null, $except = [], $tag = null)
1586
+ {
1587
+ if (!is_array($except)) {
1588
+ $tag = $except;
1589
+ $except = [];
1590
+ }
1591
+
1592
+ if (false !== $key && $this->isGet() && !$this->isCheckCache) {
1593
+ // 标记请求缓存检查
1594
+ $this->isCheckCache = true;
1595
+ if (false === $expire) {
1596
+ // 关闭当前缓存
1597
+ return;
1598
+ }
1599
+ if ($key instanceof \Closure) {
1600
+ $key = call_user_func_array($key, [$this]);
1601
+ } elseif (true === $key) {
1602
+ foreach ($except as $rule) {
1603
+ if (0 === stripos($this->url(), $rule)) {
1604
+ return;
1605
+ }
1606
+ }
1607
+ // 自动缓存功能
1608
+ $key = '__URL__';
1609
+ } elseif (strpos($key, '|')) {
1610
+ list($key, $fun) = explode('|', $key);
1611
+ }
1612
+ // 特殊规则替换
1613
+ if (false !== strpos($key, '__')) {
1614
+ $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key);
1615
+ }
1616
+
1617
+ if (false !== strpos($key, ':')) {
1618
+ $param = $this->param();
1619
+ foreach ($param as $item => $val) {
1620
+ if (is_string($val) && false !== strpos($key, ':' . $item)) {
1621
+ $key = str_replace(':' . $item, $val, $key);
1622
+ }
1623
+ }
1624
+ } elseif (strpos($key, ']')) {
1625
+ if ('[' . $this->ext() . ']' == $key) {
1626
+ // 缓存某个后缀的请求
1627
+ $key = md5($this->url());
1628
+ } else {
1629
+ return;
1630
+ }
1631
+ }
1632
+ if (isset($fun)) {
1633
+ $key = $fun($key);
1634
+ }
1635
+
1636
+ if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) {
1637
+ // 读取缓存
1638
+ $response = Response::create()->code(304);
1639
+ throw new \think\exception\HttpResponseException($response);
1640
+ } elseif (Cache::has($key)) {
1641
+ list($content, $header) = Cache::get($key);
1642
+ $response = Response::create($content)->header($header);
1643
+ throw new \think\exception\HttpResponseException($response);
1644
+ } else {
1645
+ $this->cache = [$key, $expire, $tag];
1646
+ }
1647
+ }
1648
+ }
1649
+
1650
+ /**
1651
+ * 读取请求缓存设置
1652
+ * @access public
1653
+ * @return array
1654
+ */
1655
+ public function getCache()
1656
+ {
1657
+ return $this->cache;
1658
+ }
1659
+
1660
+ /**
1661
+ * 设置当前请求绑定的对象实例
1662
+ * @access public
1663
+ * @param string|array $name 绑定的对象标识
1664
+ * @param mixed $obj 绑定的对象实例
1665
+ * @return mixed
1666
+ */
1667
+ public function bind($name, $obj = null)
1668
+ {
1669
+ if (is_array($name)) {
1670
+ $this->bind = array_merge($this->bind, $name);
1671
+ } else {
1672
+ $this->bind[$name] = $obj;
1673
+ }
1674
+ }
1675
+
1676
+ public function __set($name, $value)
1677
+ {
1678
+ $this->bind[$name] = $value;
1679
+ }
1680
+
1681
+ public function __get($name)
1682
+ {
1683
+ return isset($this->bind[$name]) ? $this->bind[$name] : null;
1684
+ }
1685
+
1686
+ public function __isset($name)
1687
+ {
1688
+ return isset($this->bind[$name]);
1689
+ }
1690
+ }
thinkphp/library/think/Response.php ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\response\Json as JsonResponse;
15
+ use think\response\Jsonp as JsonpResponse;
16
+ use think\response\Redirect as RedirectResponse;
17
+ use think\response\View as ViewResponse;
18
+ use think\response\Xml as XmlResponse;
19
+
20
+ class Response
21
+ {
22
+ // 原始数据
23
+ protected $data;
24
+
25
+ // 当前的contentType
26
+ protected $contentType = 'text/html';
27
+
28
+ // 字符集
29
+ protected $charset = 'utf-8';
30
+
31
+ //状态
32
+ protected $code = 200;
33
+
34
+ // 输出参数
35
+ protected $options = [];
36
+ // header参数
37
+ protected $header = [];
38
+
39
+ protected $content = null;
40
+
41
+ /**
42
+ * 构造函数
43
+ * @access public
44
+ * @param mixed $data 输出数据
45
+ * @param int $code
46
+ * @param array $header
47
+ * @param array $options 输出参数
48
+ */
49
+ public function __construct($data = '', $code = 200, array $header = [], $options = [])
50
+ {
51
+ $this->data($data);
52
+ if (!empty($options)) {
53
+ $this->options = array_merge($this->options, $options);
54
+ }
55
+ $this->contentType($this->contentType, $this->charset);
56
+ $this->header = array_merge($this->header, $header);
57
+ $this->code = $code;
58
+ }
59
+
60
+ /**
61
+ * 创建Response对象
62
+ * @access public
63
+ * @param mixed $data 输出数据
64
+ * @param string $type 输出类型
65
+ * @param int $code
66
+ * @param array $header
67
+ * @param array $options 输出参数
68
+ * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse
69
+ */
70
+ public static function create($data = '', $type = '', $code = 200, array $header = [], $options = [])
71
+ {
72
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
73
+ if (class_exists($class)) {
74
+ $response = new $class($data, $code, $header, $options);
75
+ } else {
76
+ $response = new static($data, $code, $header, $options);
77
+ }
78
+
79
+ return $response;
80
+ }
81
+
82
+ /**
83
+ * 发送数据到客户端
84
+ * @access public
85
+ * @return mixed
86
+ * @throws \InvalidArgumentException
87
+ */
88
+ public function send()
89
+ {
90
+ // 监听response_send
91
+ Hook::listen('response_send', $this);
92
+
93
+ // 处理输出数据
94
+ $data = $this->getContent();
95
+
96
+ // Trace调试注入
97
+ if (Env::get('app_trace', Config::get('app_trace'))) {
98
+ Debug::inject($this, $data);
99
+ }
100
+
101
+ if (200 == $this->code) {
102
+ $cache = Request::instance()->getCache();
103
+ if ($cache) {
104
+ $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate';
105
+ $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
106
+ $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT';
107
+ Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]);
108
+ }
109
+ }
110
+
111
+ if (!headers_sent() && !empty($this->header)) {
112
+ // 发送状态码
113
+ http_response_code($this->code);
114
+ // 发送头部信息
115
+ foreach ($this->header as $name => $val) {
116
+ if (is_null($val)) {
117
+ header($name);
118
+ } else {
119
+ header($name . ':' . $val);
120
+ }
121
+ }
122
+ }
123
+
124
+ echo $data;
125
+
126
+ if (function_exists('fastcgi_finish_request')) {
127
+ // 提高页面响应
128
+ fastcgi_finish_request();
129
+ }
130
+
131
+ // 监听response_end
132
+ Hook::listen('response_end', $this);
133
+
134
+ // 清空当次请求有效的数据
135
+ if (!($this instanceof RedirectResponse)) {
136
+ Session::flush();
137
+ }
138
+ }
139
+
140
+ /**
141
+ * 处理数据
142
+ * @access protected
143
+ * @param mixed $data 要处理的数据
144
+ * @return mixed
145
+ */
146
+ protected function output($data)
147
+ {
148
+ return $data;
149
+ }
150
+
151
+ /**
152
+ * 输出的参数
153
+ * @access public
154
+ * @param mixed $options 输出参数
155
+ * @return $this
156
+ */
157
+ public function options($options = [])
158
+ {
159
+ $this->options = array_merge($this->options, $options);
160
+ return $this;
161
+ }
162
+
163
+ /**
164
+ * 输出数据设置
165
+ * @access public
166
+ * @param mixed $data 输出数据
167
+ * @return $this
168
+ */
169
+ public function data($data)
170
+ {
171
+ $this->data = $data;
172
+ return $this;
173
+ }
174
+
175
+ /**
176
+ * 设置响应头
177
+ * @access public
178
+ * @param string|array $name 参数名
179
+ * @param string $value 参数值
180
+ * @return $this
181
+ */
182
+ public function header($name, $value = null)
183
+ {
184
+ if (is_array($name)) {
185
+ $this->header = array_merge($this->header, $name);
186
+ } else {
187
+ $this->header[$name] = $value;
188
+ }
189
+ return $this;
190
+ }
191
+
192
+ /**
193
+ * 设置页面输出内容
194
+ * @param $content
195
+ * @return $this
196
+ */
197
+ public function content($content)
198
+ {
199
+ if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
200
+ $content,
201
+ '__toString',
202
+ ])
203
+ ) {
204
+ throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
205
+ }
206
+
207
+ $this->content = (string) $content;
208
+
209
+ return $this;
210
+ }
211
+
212
+ /**
213
+ * 发送HTTP状态
214
+ * @param integer $code 状态码
215
+ * @return $this
216
+ */
217
+ public function code($code)
218
+ {
219
+ $this->code = $code;
220
+ return $this;
221
+ }
222
+
223
+ /**
224
+ * LastModified
225
+ * @param string $time
226
+ * @return $this
227
+ */
228
+ public function lastModified($time)
229
+ {
230
+ $this->header['Last-Modified'] = $time;
231
+ return $this;
232
+ }
233
+
234
+ /**
235
+ * Expires
236
+ * @param string $time
237
+ * @return $this
238
+ */
239
+ public function expires($time)
240
+ {
241
+ $this->header['Expires'] = $time;
242
+ return $this;
243
+ }
244
+
245
+ /**
246
+ * ETag
247
+ * @param string $eTag
248
+ * @return $this
249
+ */
250
+ public function eTag($eTag)
251
+ {
252
+ $this->header['ETag'] = $eTag;
253
+ return $this;
254
+ }
255
+
256
+ /**
257
+ * 页面缓存控制
258
+ * @param string $cache 状态码
259
+ * @return $this
260
+ */
261
+ public function cacheControl($cache)
262
+ {
263
+ $this->header['Cache-control'] = $cache;
264
+ return $this;
265
+ }
266
+
267
+ /**
268
+ * 页面输出类型
269
+ * @param string $contentType 输出类型
270
+ * @param string $charset 输出编码
271
+ * @return $this
272
+ */
273
+ public function contentType($contentType, $charset = 'utf-8')
274
+ {
275
+ $this->header['Content-Type'] = $contentType . '; charset=' . $charset;
276
+ return $this;
277
+ }
278
+
279
+ /**
280
+ * 获取头部信息
281
+ * @param string $name 头部名称
282
+ * @return mixed
283
+ */
284
+ public function getHeader($name = '')
285
+ {
286
+ if (!empty($name)) {
287
+ return isset($this->header[$name]) ? $this->header[$name] : null;
288
+ } else {
289
+ return $this->header;
290
+ }
291
+ }
292
+
293
+ /**
294
+ * 获取原始数据
295
+ * @return mixed
296
+ */
297
+ public function getData()
298
+ {
299
+ return $this->data;
300
+ }
301
+
302
+ /**
303
+ * 获取输出数据
304
+ * @return mixed
305
+ */
306
+ public function getContent()
307
+ {
308
+ if (null == $this->content) {
309
+ $content = $this->output($this->data);
310
+
311
+ if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
312
+ $content,
313
+ '__toString',
314
+ ])
315
+ ) {
316
+ throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
317
+ }
318
+
319
+ $this->content = (string) $content;
320
+ }
321
+ return $this->content;
322
+ }
323
+
324
+ /**
325
+ * 获取状态码
326
+ * @return integer
327
+ */
328
+ public function getCode()
329
+ {
330
+ return $this->code;
331
+ }
332
+ }
thinkphp/library/think/Route.php ADDED
@@ -0,0 +1,1645 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\HttpException;
15
+
16
+ class Route
17
+ {
18
+ // 路由规则
19
+ private static $rules = [
20
+ 'get' => [],
21
+ 'post' => [],
22
+ 'put' => [],
23
+ 'delete' => [],
24
+ 'patch' => [],
25
+ 'head' => [],
26
+ 'options' => [],
27
+ '*' => [],
28
+ 'alias' => [],
29
+ 'domain' => [],
30
+ 'pattern' => [],
31
+ 'name' => [],
32
+ ];
33
+
34
+ // REST路由操作方法定义
35
+ private static $rest = [
36
+ 'index' => ['get', '', 'index'],
37
+ 'create' => ['get', '/create', 'create'],
38
+ 'edit' => ['get', '/:id/edit', 'edit'],
39
+ 'read' => ['get', '/:id', 'read'],
40
+ 'save' => ['post', '', 'save'],
41
+ 'update' => ['put', '/:id', 'update'],
42
+ 'delete' => ['delete', '/:id', 'delete'],
43
+ ];
44
+
45
+ // 不同请求类型的方法前缀
46
+ private static $methodPrefix = [
47
+ 'get' => 'get',
48
+ 'post' => 'post',
49
+ 'put' => 'put',
50
+ 'delete' => 'delete',
51
+ 'patch' => 'patch',
52
+ ];
53
+
54
+ // 子域名
55
+ private static $subDomain = '';
56
+ // 域名绑定
57
+ private static $bind = [];
58
+ // 当前分组信息
59
+ private static $group = [];
60
+ // 当前子域名绑定
61
+ private static $domainBind;
62
+ private static $domainRule;
63
+ // 当前域名
64
+ private static $domain;
65
+ // 当前路由执行过程中的参数
66
+ private static $option = [];
67
+
68
+ /**
69
+ * 注册变量规则
70
+ * @access public
71
+ * @param string|array $name 变量名
72
+ * @param string $rule 变量规则
73
+ * @return void
74
+ */
75
+ public static function pattern($name = null, $rule = '')
76
+ {
77
+ if (is_array($name)) {
78
+ self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name);
79
+ } else {
80
+ self::$rules['pattern'][$name] = $rule;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 注册子域名部署规则
86
+ * @access public
87
+ * @param string|array $domain 子域名
88
+ * @param mixed $rule 路由规则
89
+ * @param array $option 路由参数
90
+ * @param array $pattern 变量规则
91
+ * @return void
92
+ */
93
+ public static function domain($domain, $rule = '', $option = [], $pattern = [])
94
+ {
95
+ if (is_array($domain)) {
96
+ foreach ($domain as $key => $item) {
97
+ self::domain($key, $item, $option, $pattern);
98
+ }
99
+ } elseif ($rule instanceof \Closure) {
100
+ // 执行闭包
101
+ self::setDomain($domain);
102
+ call_user_func_array($rule, []);
103
+ self::setDomain(null);
104
+ } elseif (is_array($rule)) {
105
+ self::setDomain($domain);
106
+ self::group('', function () use ($rule) {
107
+ // 动态注册域名的路由规则
108
+ self::registerRules($rule);
109
+ }, $option, $pattern);
110
+ self::setDomain(null);
111
+ } else {
112
+ self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern];
113
+ }
114
+ }
115
+
116
+ private static function setDomain($domain)
117
+ {
118
+ self::$domain = $domain;
119
+ }
120
+
121
+ /**
122
+ * 设置路由绑定
123
+ * @access public
124
+ * @param mixed $bind 绑定信息
125
+ * @param string $type 绑定类型 默认为module 支持 namespace class controller
126
+ * @return mixed
127
+ */
128
+ public static function bind($bind, $type = 'module')
129
+ {
130
+ self::$bind = ['type' => $type, $type => $bind];
131
+ }
132
+
133
+ /**
134
+ * 设置或者获取路由标识
135
+ * @access public
136
+ * @param string|array $name 路由命名标识 数组表示批量设置
137
+ * @param array $value 路由地址及变量信息
138
+ * @return array
139
+ */
140
+ public static function name($name = '', $value = null)
141
+ {
142
+ if (is_array($name)) {
143
+ return self::$rules['name'] = $name;
144
+ } elseif ('' === $name) {
145
+ return self::$rules['name'];
146
+ } elseif (!is_null($value)) {
147
+ self::$rules['name'][strtolower($name)][] = $value;
148
+ } else {
149
+ $name = strtolower($name);
150
+ return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 读取路由绑定
156
+ * @access public
157
+ * @param string $type 绑定类型
158
+ * @return mixed
159
+ */
160
+ public static function getBind($type)
161
+ {
162
+ return isset(self::$bind[$type]) ? self::$bind[$type] : null;
163
+ }
164
+
165
+ /**
166
+ * 导入配置文件的路由规则
167
+ * @access public
168
+ * @param array $rule 路由规则
169
+ * @param string $type 请求类型
170
+ * @return void
171
+ */
172
+ public static function import(array $rule, $type = '*')
173
+ {
174
+ // 检查域名部署
175
+ if (isset($rule['__domain__'])) {
176
+ self::domain($rule['__domain__']);
177
+ unset($rule['__domain__']);
178
+ }
179
+
180
+ // 检查变量规则
181
+ if (isset($rule['__pattern__'])) {
182
+ self::pattern($rule['__pattern__']);
183
+ unset($rule['__pattern__']);
184
+ }
185
+
186
+ // 检查路由别名
187
+ if (isset($rule['__alias__'])) {
188
+ self::alias($rule['__alias__']);
189
+ unset($rule['__alias__']);
190
+ }
191
+
192
+ // 检查资源路由
193
+ if (isset($rule['__rest__'])) {
194
+ self::resource($rule['__rest__']);
195
+ unset($rule['__rest__']);
196
+ }
197
+
198
+ self::registerRules($rule, strtolower($type));
199
+ }
200
+
201
+ // 批量注册路由
202
+ protected static function registerRules($rules, $type = '*')
203
+ {
204
+ foreach ($rules as $key => $val) {
205
+ if (is_numeric($key)) {
206
+ $key = array_shift($val);
207
+ }
208
+ if (empty($val)) {
209
+ continue;
210
+ }
211
+ if (is_string($key) && 0 === strpos($key, '[')) {
212
+ $key = substr($key, 1, -1);
213
+ self::group($key, $val);
214
+ } elseif (is_array($val)) {
215
+ self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []);
216
+ } else {
217
+ self::setRule($key, $val, $type);
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * 注册路由规则
224
+ * @access public
225
+ * @param string|array $rule 路由规则
226
+ * @param string $route 路由地址
227
+ * @param string $type 请求类型
228
+ * @param array $option 路由参数
229
+ * @param array $pattern 变量规则
230
+ * @return void
231
+ */
232
+ public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = [])
233
+ {
234
+ $group = self::getGroup('name');
235
+
236
+ if (!is_null($group)) {
237
+ // 路由分组
238
+ $option = array_merge(self::getGroup('option'), $option);
239
+ $pattern = array_merge(self::getGroup('pattern'), $pattern);
240
+ }
241
+
242
+ $type = strtolower($type);
243
+
244
+ if (strpos($type, '|')) {
245
+ $option['method'] = $type;
246
+ $type = '*';
247
+ }
248
+ if (is_array($rule) && empty($route)) {
249
+ foreach ($rule as $key => $val) {
250
+ if (is_numeric($key)) {
251
+ $key = array_shift($val);
252
+ }
253
+ if (is_array($val)) {
254
+ $route = $val[0];
255
+ $option1 = array_merge($option, $val[1]);
256
+ $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
257
+ } else {
258
+ $option1 = null;
259
+ $pattern1 = null;
260
+ $route = $val;
261
+ }
262
+ self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group);
263
+ }
264
+ } else {
265
+ self::setRule($rule, $route, $type, $option, $pattern, $group);
266
+ }
267
+
268
+ }
269
+
270
+ /**
271
+ * 设置路由规则
272
+ * @access public
273
+ * @param string|array $rule 路由规则
274
+ * @param string $route 路由地址
275
+ * @param string $type 请求类型
276
+ * @param array $option 路由参数
277
+ * @param array $pattern 变量规则
278
+ * @param string $group 所属分组
279
+ * @return void
280
+ */
281
+ protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '')
282
+ {
283
+ if (is_array($rule)) {
284
+ $name = $rule[0];
285
+ $rule = $rule[1];
286
+ } elseif (is_string($route)) {
287
+ $name = $route;
288
+ }
289
+ if (!isset($option['complete_match'])) {
290
+ if (Config::get('route_complete_match')) {
291
+ $option['complete_match'] = true;
292
+ } elseif ('$' == substr($rule, -1, 1)) {
293
+ // 是否完整匹配
294
+ $option['complete_match'] = true;
295
+ }
296
+ } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) {
297
+ // 是否完整匹配
298
+ $option['complete_match'] = true;
299
+ }
300
+
301
+ if ('$' == substr($rule, -1, 1)) {
302
+ $rule = substr($rule, 0, -1);
303
+ }
304
+
305
+ if ('/' != $rule || $group) {
306
+ $rule = trim($rule, '/');
307
+ }
308
+ $vars = self::parseVar($rule);
309
+ if (isset($name)) {
310
+ $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule;
311
+ $suffix = isset($option['ext']) ? $option['ext'] : null;
312
+ self::name($name, [$key, $vars, self::$domain, $suffix]);
313
+ }
314
+ if (isset($option['modular'])) {
315
+ $route = $option['modular'] . '/' . $route;
316
+ }
317
+ if ($group) {
318
+ if ('*' != $type) {
319
+ $option['method'] = $type;
320
+ }
321
+ if (self::$domain) {
322
+ self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
323
+ } else {
324
+ self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
325
+ }
326
+ } else {
327
+ if ('*' != $type && isset(self::$rules['*'][$rule])) {
328
+ unset(self::$rules['*'][$rule]);
329
+ }
330
+ if (self::$domain) {
331
+ self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
332
+ } else {
333
+ self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
334
+ }
335
+ if ('*' == $type) {
336
+ // 注册路由快捷方式
337
+ foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
338
+ if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) {
339
+ self::$rules['domain'][self::$domain][$method][$rule] = true;
340
+ } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) {
341
+ self::$rules[$method][$rule] = true;
342
+ }
343
+ }
344
+ }
345
+ }
346
+ }
347
+
348
+ /**
349
+ * 设置当前执行的参数信息
350
+ * @access public
351
+ * @param array $options 参数信息
352
+ * @return mixed
353
+ */
354
+ protected static function setOption($options = [])
355
+ {
356
+ self::$option[] = $options;
357
+ }
358
+
359
+ /**
360
+ * 获取当前执行的所有参数信息
361
+ * @access public
362
+ * @return array
363
+ */
364
+ public static function getOption()
365
+ {
366
+ return self::$option;
367
+ }
368
+
369
+ /**
370
+ * 获取当前的分组信息
371
+ * @access public
372
+ * @param string $type 分组信息名称 name option pattern
373
+ * @return mixed
374
+ */
375
+ public static function getGroup($type)
376
+ {
377
+ if (isset(self::$group[$type])) {
378
+ return self::$group[$type];
379
+ } else {
380
+ return 'name' == $type ? null : [];
381
+ }
382
+ }
383
+
384
+ /**
385
+ * 设置当前的路由分组
386
+ * @access public
387
+ * @param string $name 分组名称
388
+ * @param array $option 分组路由参数
389
+ * @param array $pattern 分组变量规则
390
+ * @return void
391
+ */
392
+ public static function setGroup($name, $option = [], $pattern = [])
393
+ {
394
+ self::$group['name'] = $name;
395
+ self::$group['option'] = $option ?: [];
396
+ self::$group['pattern'] = $pattern ?: [];
397
+ }
398
+
399
+ /**
400
+ * 注册路由分组
401
+ * @access public
402
+ * @param string|array $name 分组名称或者参数
403
+ * @param array|\Closure $routes 路由地址
404
+ * @param array $option 路由参数
405
+ * @param array $pattern 变量规则
406
+ * @return void
407
+ */
408
+ public static function group($name, $routes, $option = [], $pattern = [])
409
+ {
410
+ if (is_array($name)) {
411
+ $option = $name;
412
+ $name = isset($option['name']) ? $option['name'] : '';
413
+ }
414
+ // 分组
415
+ $currentGroup = self::getGroup('name');
416
+ if ($currentGroup) {
417
+ $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : '');
418
+ }
419
+ if (!empty($name)) {
420
+ if ($routes instanceof \Closure) {
421
+ $currentOption = self::getGroup('option');
422
+ $currentPattern = self::getGroup('pattern');
423
+ self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
424
+ call_user_func_array($routes, []);
425
+ self::setGroup($currentGroup, $currentOption, $currentPattern);
426
+ if ($currentGroup != $name) {
427
+ self::$rules['*'][$name]['route'] = '';
428
+ self::$rules['*'][$name]['var'] = self::parseVar($name);
429
+ self::$rules['*'][$name]['option'] = $option;
430
+ self::$rules['*'][$name]['pattern'] = $pattern;
431
+ }
432
+ } else {
433
+ $item = [];
434
+ $completeMatch = Config::get('route_complete_match');
435
+ foreach ($routes as $key => $val) {
436
+ if (is_numeric($key)) {
437
+ $key = array_shift($val);
438
+ }
439
+ if (is_array($val)) {
440
+ $route = $val[0];
441
+ $option1 = array_merge($option, isset($val[1]) ? $val[1] : []);
442
+ $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
443
+ } else {
444
+ $route = $val;
445
+ }
446
+
447
+ $options = isset($option1) ? $option1 : $option;
448
+ $patterns = isset($pattern1) ? $pattern1 : $pattern;
449
+ if ('$' == substr($key, -1, 1)) {
450
+ // 是否完整匹配
451
+ $options['complete_match'] = true;
452
+ $key = substr($key, 0, -1);
453
+ } elseif ($completeMatch) {
454
+ $options['complete_match'] = true;
455
+ }
456
+ $key = trim($key, '/');
457
+ $vars = self::parseVar($key);
458
+ $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns];
459
+ // 设置路由标识
460
+ $suffix = isset($options['ext']) ? $options['ext'] : null;
461
+ self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]);
462
+ }
463
+ self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern];
464
+ }
465
+
466
+ foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
467
+ if (!isset(self::$rules[$method][$name])) {
468
+ self::$rules[$method][$name] = true;
469
+ } elseif (is_array(self::$rules[$method][$name])) {
470
+ self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]);
471
+ }
472
+ }
473
+
474
+ } elseif ($routes instanceof \Closure) {
475
+ // 闭包注册
476
+ $currentOption = self::getGroup('option');
477
+ $currentPattern = self::getGroup('pattern');
478
+ self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
479
+ call_user_func_array($routes, []);
480
+ self::setGroup($currentGroup, $currentOption, $currentPattern);
481
+ } else {
482
+ // 批量注册路由
483
+ self::rule($routes, '', '*', $option, $pattern);
484
+ }
485
+ }
486
+
487
+ /**
488
+ * 注册路由
489
+ * @access public
490
+ * @param string|array $rule 路由规则
491
+ * @param string $route 路由地址
492
+ * @param array $option 路由参数
493
+ * @param array $pattern 变量规则
494
+ * @return void
495
+ */
496
+ public static function any($rule, $route = '', $option = [], $pattern = [])
497
+ {
498
+ self::rule($rule, $route, '*', $option, $pattern);
499
+ }
500
+
501
+ /**
502
+ * 注册GET路由
503
+ * @access public
504
+ * @param string|array $rule 路由规则
505
+ * @param string $route 路由地址
506
+ * @param array $option 路由参数
507
+ * @param array $pattern 变量规则
508
+ * @return void
509
+ */
510
+ public static function get($rule, $route = '', $option = [], $pattern = [])
511
+ {
512
+ self::rule($rule, $route, 'GET', $option, $pattern);
513
+ }
514
+
515
+ /**
516
+ * 注册POST路由
517
+ * @access public
518
+ * @param string|array $rule 路由规则
519
+ * @param string $route 路由地址
520
+ * @param array $option 路由参数
521
+ * @param array $pattern 变量规则
522
+ * @return void
523
+ */
524
+ public static function post($rule, $route = '', $option = [], $pattern = [])
525
+ {
526
+ self::rule($rule, $route, 'POST', $option, $pattern);
527
+ }
528
+
529
+ /**
530
+ * 注册PUT路由
531
+ * @access public
532
+ * @param string|array $rule 路由规则
533
+ * @param string $route 路由地址
534
+ * @param array $option 路由参数
535
+ * @param array $pattern 变量规则
536
+ * @return void
537
+ */
538
+ public static function put($rule, $route = '', $option = [], $pattern = [])
539
+ {
540
+ self::rule($rule, $route, 'PUT', $option, $pattern);
541
+ }
542
+
543
+ /**
544
+ * 注册DELETE路由
545
+ * @access public
546
+ * @param string|array $rule 路由规则
547
+ * @param string $route 路由地址
548
+ * @param array $option 路由参数
549
+ * @param array $pattern 变量规则
550
+ * @return void
551
+ */
552
+ public static function delete($rule, $route = '', $option = [], $pattern = [])
553
+ {
554
+ self::rule($rule, $route, 'DELETE', $option, $pattern);
555
+ }
556
+
557
+ /**
558
+ * 注册PATCH路由
559
+ * @access public
560
+ * @param string|array $rule 路由规则
561
+ * @param string $route 路由地址
562
+ * @param array $option 路由参数
563
+ * @param array $pattern 变量规则
564
+ * @return void
565
+ */
566
+ public static function patch($rule, $route = '', $option = [], $pattern = [])
567
+ {
568
+ self::rule($rule, $route, 'PATCH', $option, $pattern);
569
+ }
570
+
571
+ /**
572
+ * 注册资源路由
573
+ * @access public
574
+ * @param string|array $rule 路由规则
575
+ * @param string $route 路由地址
576
+ * @param array $option 路由参数
577
+ * @param array $pattern 变量规则
578
+ * @return void
579
+ */
580
+ public static function resource($rule, $route = '', $option = [], $pattern = [])
581
+ {
582
+ if (is_array($rule)) {
583
+ foreach ($rule as $key => $val) {
584
+ if (is_array($val)) {
585
+ list($val, $option, $pattern) = array_pad($val, 3, []);
586
+ }
587
+ self::resource($key, $val, $option, $pattern);
588
+ }
589
+ } else {
590
+ if (strpos($rule, '.')) {
591
+ // 注册嵌套资源路由
592
+ $array = explode('.', $rule);
593
+ $last = array_pop($array);
594
+ $item = [];
595
+ foreach ($array as $val) {
596
+ $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id');
597
+ }
598
+ $rule = implode('/', $item) . '/' . $last;
599
+ }
600
+ // 注册资源路由
601
+ foreach (self::$rest as $key => $val) {
602
+ if ((isset($option['only']) && !in_array($key, $option['only']))
603
+ || (isset($option['except']) && in_array($key, $option['except']))) {
604
+ continue;
605
+ }
606
+ if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) {
607
+ $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]);
608
+ } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) {
609
+ $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]);
610
+ }
611
+ $item = ltrim($rule . $val[1], '/');
612
+ $option['rest'] = $key;
613
+ self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern);
614
+ }
615
+ }
616
+ }
617
+
618
+ /**
619
+ * 注册控制器路由 操作方法对应不同的请求后缀
620
+ * @access public
621
+ * @param string $rule 路由规则
622
+ * @param string $route 路由地址
623
+ * @param array $option 路由参数
624
+ * @param array $pattern 变量规则
625
+ * @return void
626
+ */
627
+ public static function controller($rule, $route = '', $option = [], $pattern = [])
628
+ {
629
+ foreach (self::$methodPrefix as $type => $val) {
630
+ self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern);
631
+ }
632
+ }
633
+
634
+ /**
635
+ * 注册别名路由
636
+ * @access public
637
+ * @param string|array $rule 路由别名
638
+ * @param string $route 路由地址
639
+ * @param array $option 路由参数
640
+ * @return void
641
+ */
642
+ public static function alias($rule = null, $route = '', $option = [])
643
+ {
644
+ if (is_array($rule)) {
645
+ self::$rules['alias'] = array_merge(self::$rules['alias'], $rule);
646
+ } else {
647
+ self::$rules['alias'][$rule] = $option ? [$route, $option] : $route;
648
+ }
649
+ }
650
+
651
+ /**
652
+ * 设置不同请求类型下面的方法前缀
653
+ * @access public
654
+ * @param string $method 请求类型
655
+ * @param string $prefix 类型前缀
656
+ * @return void
657
+ */
658
+ public static function setMethodPrefix($method, $prefix = '')
659
+ {
660
+ if (is_array($method)) {
661
+ self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method));
662
+ } else {
663
+ self::$methodPrefix[strtolower($method)] = $prefix;
664
+ }
665
+ }
666
+
667
+ /**
668
+ * rest方法定义和修改
669
+ * @access public
670
+ * @param string|array $name 方法名称
671
+ * @param array|bool $resource 资源
672
+ * @return void
673
+ */
674
+ public static function rest($name, $resource = [])
675
+ {
676
+ if (is_array($name)) {
677
+ self::$rest = $resource ? $name : array_merge(self::$rest, $name);
678
+ } else {
679
+ self::$rest[$name] = $resource;
680
+ }
681
+ }
682
+
683
+ /**
684
+ * 注册未匹配路由规则后的处理
685
+ * @access public
686
+ * @param string $route 路由地址
687
+ * @param string $method 请求类型
688
+ * @param array $option 路由参数
689
+ * @return void
690
+ */
691
+ public static function miss($route, $method = '*', $option = [])
692
+ {
693
+ self::rule('__miss__', $route, $method, $option, []);
694
+ }
695
+
696
+ /**
697
+ * 注册一个自动解析的URL路由
698
+ * @access public
699
+ * @param string $route 路由地址
700
+ * @return void
701
+ */
702
+ public static function auto($route)
703
+ {
704
+ self::rule('__auto__', $route, '*', [], []);
705
+ }
706
+
707
+ /**
708
+ * 获取或者批量设置路由定义
709
+ * @access public
710
+ * @param mixed $rules 请求类型或者路由定义数组
711
+ * @return array
712
+ */
713
+ public static function rules($rules = '')
714
+ {
715
+ if (is_array($rules)) {
716
+ self::$rules = $rules;
717
+ } elseif ($rules) {
718
+ return true === $rules ? self::$rules : self::$rules[strtolower($rules)];
719
+ } else {
720
+ $rules = self::$rules;
721
+ unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']);
722
+ return $rules;
723
+ }
724
+ }
725
+
726
+ /**
727
+ * 检测子域名部署
728
+ * @access public
729
+ * @param Request $request Request请求对象
730
+ * @param array $currentRules 当前路由规则
731
+ * @param string $method 请求类型
732
+ * @return void
733
+ */
734
+ public static function checkDomain($request, &$currentRules, $method = 'get')
735
+ {
736
+ // 域名规则
737
+ $rules = self::$rules['domain'];
738
+ // 开启子域名部署 支持二级和三级域名
739
+ if (!empty($rules)) {
740
+ $host = $request->host(true);
741
+ if (isset($rules[$host])) {
742
+ // 完整域名或者IP配置
743
+ $item = $rules[$host];
744
+ } else {
745
+ $rootDomain = Config::get('url_domain_root');
746
+ if ($rootDomain) {
747
+ // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置
748
+ $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.'));
749
+ } else {
750
+ $domain = explode('.', $host, -2);
751
+ }
752
+ // 子域名配置
753
+ if (!empty($domain)) {
754
+ // 当前子域名
755
+ $subDomain = implode('.', $domain);
756
+ self::$subDomain = $subDomain;
757
+ $domain2 = array_pop($domain);
758
+ if ($domain) {
759
+ // 存在三级域名
760
+ $domain3 = array_pop($domain);
761
+ }
762
+ if ($subDomain && isset($rules[$subDomain])) {
763
+ // 子域名配置
764
+ $item = $rules[$subDomain];
765
+ } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) {
766
+ // 泛三级域名
767
+ $item = $rules['*.' . $domain2];
768
+ $panDomain = $domain3;
769
+ } elseif (isset($rules['*']) && !empty($domain2)) {
770
+ // 泛二级域名
771
+ if ('www' != $domain2) {
772
+ $item = $rules['*'];
773
+ $panDomain = $domain2;
774
+ }
775
+ }
776
+ }
777
+ }
778
+ if (!empty($item)) {
779
+ if (isset($panDomain)) {
780
+ // 保存当前泛域名
781
+ $request->route(['__domain__' => $panDomain]);
782
+ }
783
+ if (isset($item['[bind]'])) {
784
+ // 解析子域名部署规则
785
+ list($rule, $option, $pattern) = $item['[bind]'];
786
+ if (!empty($option['https']) && !$request->isSsl()) {
787
+ // https检测
788
+ throw new HttpException(404, 'must use https request:' . $host);
789
+ }
790
+
791
+ if (strpos($rule, '?')) {
792
+ // 传入其它参数
793
+ $array = parse_url($rule);
794
+ $result = $array['path'];
795
+ parse_str($array['query'], $params);
796
+ if (isset($panDomain)) {
797
+ $pos = array_search('*', $params);
798
+ if (false !== $pos) {
799
+ // 泛域名作为参数
800
+ $params[$pos] = $panDomain;
801
+ }
802
+ }
803
+ $_GET = array_merge($_GET, $params);
804
+ } else {
805
+ $result = $rule;
806
+ }
807
+
808
+ if (0 === strpos($result, '\\')) {
809
+ // 绑定到命名空间 例如 \app\index\behavior
810
+ self::$bind = ['type' => 'namespace', 'namespace' => $result];
811
+ } elseif (0 === strpos($result, '@')) {
812
+ // 绑定到类 例如 @app\index\controller\User
813
+ self::$bind = ['type' => 'class', 'class' => substr($result, 1)];
814
+ } else {
815
+ // 绑定到模块/控制器 例如 index/user
816
+ self::$bind = ['type' => 'module', 'module' => $result];
817
+ }
818
+ self::$domainBind = true;
819
+ } else {
820
+ self::$domainRule = $item;
821
+ $currentRules = isset($item[$method]) ? $item[$method] : $item['*'];
822
+ }
823
+ }
824
+ }
825
+ }
826
+
827
+ /**
828
+ * 检测URL路由
829
+ * @access public
830
+ * @param Request $request Request请求对象
831
+ * @param string $url URL地址
832
+ * @param string $depr URL分隔符
833
+ * @param bool $checkDomain 是否检测域名规则
834
+ * @return false|array
835
+ */
836
+ public static function check($request, $url, $depr = '/', $checkDomain = false)
837
+ {
838
+ //检查解析缓存
839
+ if (!App::$debug && Config::get('route_check_cache')) {
840
+ $key = self::getCheckCacheKey($request);
841
+ if (Cache::has($key)) {
842
+ list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key);
843
+ return self::parseRule($rule, $route, $pathinfo, $option, $matches, true);
844
+ }
845
+ }
846
+
847
+ // 分隔符替换 确保路由定义使用统一的分隔符
848
+ $url = str_replace($depr, '|', $url);
849
+
850
+ if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) {
851
+ // 检测路由别名
852
+ $result = self::checkRouteAlias($request, $url, $depr);
853
+ if (false !== $result) {
854
+ return $result;
855
+ }
856
+ }
857
+ $method = strtolower($request->method());
858
+ // 获取当前请求类型的路由规则
859
+ $rules = isset(self::$rules[$method]) ? self::$rules[$method] : [];
860
+ // 检测域名部署
861
+ if ($checkDomain) {
862
+ self::checkDomain($request, $rules, $method);
863
+ }
864
+ // 检测URL绑定
865
+ $return = self::checkUrlBind($url, $rules, $depr);
866
+ if (false !== $return) {
867
+ return $return;
868
+ }
869
+ if ('|' != $url) {
870
+ $url = rtrim($url, '|');
871
+ }
872
+ $item = str_replace('|', '/', $url);
873
+ if (isset($rules[$item])) {
874
+ // 静态路由规则检测
875
+ $rule = $rules[$item];
876
+ if (true === $rule) {
877
+ $rule = self::getRouteExpress($item);
878
+ }
879
+ if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) {
880
+ self::setOption($rule['option']);
881
+ return self::parseRule($item, $rule['route'], $url, $rule['option']);
882
+ }
883
+ }
884
+
885
+ // 路由规则检测
886
+ if (!empty($rules)) {
887
+ return self::checkRoute($request, $rules, $url, $depr);
888
+ }
889
+ return false;
890
+ }
891
+
892
+ private static function getRouteExpress($key)
893
+ {
894
+ return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key];
895
+ }
896
+
897
+ /**
898
+ * 检测路由规则
899
+ * @access private
900
+ * @param Request $request
901
+ * @param array $rules 路由规则
902
+ * @param string $url URL地址
903
+ * @param string $depr URL分割符
904
+ * @param string $group 路由分组名
905
+ * @param array $options 路由参数(分组)
906
+ * @return mixed
907
+ */
908
+ private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = [])
909
+ {
910
+ foreach ($rules as $key => $item) {
911
+ if (true === $item) {
912
+ $item = self::getRouteExpress($key);
913
+ }
914
+ if (!isset($item['rule'])) {
915
+ continue;
916
+ }
917
+ $rule = $item['rule'];
918
+ $route = $item['route'];
919
+ $vars = $item['var'];
920
+ $option = $item['option'];
921
+ $pattern = $item['pattern'];
922
+
923
+ // 检查参数有效性
924
+ if (!self::checkOption($option, $request)) {
925
+ continue;
926
+ }
927
+
928
+ if (isset($option['ext'])) {
929
+ // 路由ext参数 优先于系统配置的URL伪静态后缀参数
930
+ $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url);
931
+ }
932
+
933
+ if (is_array($rule)) {
934
+ // 分组路由
935
+ $pos = strpos(str_replace('<', ':', $key), ':');
936
+ if (false !== $pos) {
937
+ $str = substr($key, 0, $pos);
938
+ } else {
939
+ $str = $key;
940
+ }
941
+ if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
942
+ continue;
943
+ }
944
+ self::setOption($option);
945
+ $result = self::checkRoute($request, $rule, $url, $depr, $key, $option);
946
+ if (false !== $result) {
947
+ return $result;
948
+ }
949
+ } elseif ($route) {
950
+ if ('__miss__' == $rule || '__auto__' == $rule) {
951
+ // 指定特殊路由
952
+ $var = trim($rule, '__');
953
+ ${$var} = $item;
954
+ continue;
955
+ }
956
+ if ($group) {
957
+ $rule = $group . ($rule ? '/' . ltrim($rule, '/') : '');
958
+ }
959
+
960
+ self::setOption($option);
961
+ if (isset($options['bind_model']) && isset($option['bind_model'])) {
962
+ $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']);
963
+ }
964
+ $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr);
965
+ if (false !== $result) {
966
+ return $result;
967
+ }
968
+ }
969
+ }
970
+ if (isset($auto)) {
971
+ // 自动解析URL地址
972
+ return self::parseUrl($auto['route'] . '/' . $url, $depr);
973
+ } elseif (isset($miss)) {
974
+ // 未匹配所有路由的路由规则处理
975
+ return self::parseRule('', $miss['route'], $url, $miss['option']);
976
+ }
977
+ return false;
978
+ }
979
+
980
+ /**
981
+ * 检测路由别名
982
+ * @access private
983
+ * @param Request $request
984
+ * @param string $url URL地址
985
+ * @param string $depr URL分隔符
986
+ * @return mixed
987
+ */
988
+ private static function checkRouteAlias($request, $url, $depr)
989
+ {
990
+ $array = explode('|', $url);
991
+ $alias = array_shift($array);
992
+ $item = self::$rules['alias'][$alias];
993
+
994
+ if (is_array($item)) {
995
+ list($rule, $option) = $item;
996
+ $action = $array[0];
997
+ if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) {
998
+ // 允许操作
999
+ return false;
1000
+ } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) {
1001
+ // 排除操作
1002
+ return false;
1003
+ }
1004
+ if (isset($option['method'][$action])) {
1005
+ $option['method'] = $option['method'][$action];
1006
+ }
1007
+ } else {
1008
+ $rule = $item;
1009
+ }
1010
+ $bind = implode('|', $array);
1011
+ // 参数有效性检查
1012
+ if (isset($option) && !self::checkOption($option, $request)) {
1013
+ // 路由不匹配
1014
+ return false;
1015
+ } elseif (0 === strpos($rule, '\\')) {
1016
+ // 路由到类
1017
+ return self::bindToClass($bind, substr($rule, 1), $depr);
1018
+ } elseif (0 === strpos($rule, '@')) {
1019
+ // 路由到控制器类
1020
+ return self::bindToController($bind, substr($rule, 1), $depr);
1021
+ } else {
1022
+ // 路由到模块/控制器
1023
+ return self::bindToModule($bind, $rule, $depr);
1024
+ }
1025
+ }
1026
+
1027
+ /**
1028
+ * 检测URL绑定
1029
+ * @access private
1030
+ * @param string $url URL地址
1031
+ * @param array $rules 路由规则
1032
+ * @param string $depr URL分隔符
1033
+ * @return mixed
1034
+ */
1035
+ private static function checkUrlBind(&$url, &$rules, $depr = '/')
1036
+ {
1037
+ if (!empty(self::$bind)) {
1038
+ $type = self::$bind['type'];
1039
+ $bind = self::$bind[$type];
1040
+ // 记录绑定信息
1041
+ App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info');
1042
+ // 如果有URL绑定 则进行绑定检测
1043
+ switch ($type) {
1044
+ case 'class':
1045
+ // 绑定到类
1046
+ return self::bindToClass($url, $bind, $depr);
1047
+ case 'controller':
1048
+ // 绑定到控制器类
1049
+ return self::bindToController($url, $bind, $depr);
1050
+ case 'namespace':
1051
+ // 绑定到命名空间
1052
+ return self::bindToNamespace($url, $bind, $depr);
1053
+ }
1054
+ }
1055
+ return false;
1056
+ }
1057
+
1058
+ /**
1059
+ * 绑定到类
1060
+ * @access public
1061
+ * @param string $url URL地址
1062
+ * @param string $class 类名(带命名空间)
1063
+ * @param string $depr URL分隔符
1064
+ * @return array
1065
+ */
1066
+ public static function bindToClass($url, $class, $depr = '/')
1067
+ {
1068
+ $url = str_replace($depr, '|', $url);
1069
+ $array = explode('|', $url, 2);
1070
+ $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
1071
+ if (!empty($array[1])) {
1072
+ self::parseUrlParams($array[1]);
1073
+ }
1074
+ return ['type' => 'method', 'method' => [$class, $action], 'var' => []];
1075
+ }
1076
+
1077
+ /**
1078
+ * 绑定到命名空间
1079
+ * @access public
1080
+ * @param string $url URL地址
1081
+ * @param string $namespace 命名空间
1082
+ * @param string $depr URL分隔符
1083
+ * @return array
1084
+ */
1085
+ public static function bindToNamespace($url, $namespace, $depr = '/')
1086
+ {
1087
+ $url = str_replace($depr, '|', $url);
1088
+ $array = explode('|', $url, 3);
1089
+ $class = !empty($array[0]) ? $array[0] : Config::get('default_controller');
1090
+ $method = !empty($array[1]) ? $array[1] : Config::get('default_action');
1091
+ if (!empty($array[2])) {
1092
+ self::parseUrlParams($array[2]);
1093
+ }
1094
+ return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []];
1095
+ }
1096
+
1097
+ /**
1098
+ * 绑定到控制器类
1099
+ * @access public
1100
+ * @param string $url URL地址
1101
+ * @param string $controller 控制器名 (支持带模块名 index/user )
1102
+ * @param string $depr URL分隔符
1103
+ * @return array
1104
+ */
1105
+ public static function bindToController($url, $controller, $depr = '/')
1106
+ {
1107
+ $url = str_replace($depr, '|', $url);
1108
+ $array = explode('|', $url, 2);
1109
+ $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
1110
+ if (!empty($array[1])) {
1111
+ self::parseUrlParams($array[1]);
1112
+ }
1113
+ return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []];
1114
+ }
1115
+
1116
+ /**
1117
+ * 绑定到模块/控制器
1118
+ * @access public
1119
+ * @param string $url URL地址
1120
+ * @param string $controller 控制器类名(带命名空间)
1121
+ * @param string $depr URL分隔符
1122
+ * @return array
1123
+ */
1124
+ public static function bindToModule($url, $controller, $depr = '/')
1125
+ {
1126
+ $url = str_replace($depr, '|', $url);
1127
+ $array = explode('|', $url, 2);
1128
+ $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
1129
+ if (!empty($array[1])) {
1130
+ self::parseUrlParams($array[1]);
1131
+ }
1132
+ return ['type' => 'module', 'module' => $controller . '/' . $action];
1133
+ }
1134
+
1135
+ /**
1136
+ * 路由参数有效性检查
1137
+ * @access private
1138
+ * @param array $option 路由参数
1139
+ * @param Request $request Request对象
1140
+ * @return bool
1141
+ */
1142
+ private static function checkOption($option, $request)
1143
+ {
1144
+ if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method()))
1145
+ || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测
1146
+ || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测
1147
+ || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测
1148
+ || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测
1149
+ || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测
1150
+ || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|'))
1151
+ || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测
1152
+ || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测
1153
+ || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测
1154
+ || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测
1155
+ || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测
1156
+ ) {
1157
+ return false;
1158
+ }
1159
+ return true;
1160
+ }
1161
+
1162
+ /**
1163
+ * 检测路由规则
1164
+ * @access private
1165
+ * @param string $rule 路由规则
1166
+ * @param string $route 路由地址
1167
+ * @param string $url URL地址
1168
+ * @param array $pattern 变量规则
1169
+ * @param array $option 路由参数
1170
+ * @param string $depr URL分隔符(全局)
1171
+ * @return array|false
1172
+ */
1173
+ private static function checkRule($rule, $route, $url, $pattern, $option, $depr)
1174
+ {
1175
+ // 检查完整规则定义
1176
+ if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
1177
+ return false;
1178
+ }
1179
+ // 检查路由的参数分隔符
1180
+ if (isset($option['param_depr'])) {
1181
+ $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url);
1182
+ }
1183
+
1184
+ $len1 = substr_count($url, '|');
1185
+ $len2 = substr_count($rule, '/');
1186
+ // 多余参数是否合并
1187
+ $merge = !empty($option['merge_extra_vars']);
1188
+ if ($merge && $len1 > $len2) {
1189
+ $url = str_replace('|', $depr, $url);
1190
+ $url = implode('|', explode($depr, $url, $len2 + 1));
1191
+ }
1192
+
1193
+ if ($len1 >= $len2 || strpos($rule, '[')) {
1194
+ if (!empty($option['complete_match'])) {
1195
+ // 完整匹配
1196
+ if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) {
1197
+ return false;
1198
+ }
1199
+ }
1200
+ $pattern = array_merge(self::$rules['pattern'], $pattern);
1201
+ if (false !== $match = self::match($url, $rule, $pattern)) {
1202
+ // 匹配到路由规则
1203
+ return self::parseRule($rule, $route, $url, $option, $match);
1204
+ }
1205
+ }
1206
+ return false;
1207
+ }
1208
+
1209
+ /**
1210
+ * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2...
1211
+ * @access public
1212
+ * @param string $url URL地址
1213
+ * @param string $depr URL分隔符
1214
+ * @param bool $autoSearch 是否自动深度搜索控制器
1215
+ * @return array
1216
+ */
1217
+ public static function parseUrl($url, $depr = '/', $autoSearch = false)
1218
+ {
1219
+
1220
+ if (isset(self::$bind['module'])) {
1221
+ $bind = str_replace('/', $depr, self::$bind['module']);
1222
+ // 如果有模块/控制器绑定
1223
+ $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
1224
+ }
1225
+ $url = str_replace($depr, '|', $url);
1226
+ list($path, $var) = self::parseUrlPath($url);
1227
+ $route = [null, null, null];
1228
+ if (isset($path)) {
1229
+ // 解析模块
1230
+ $module = Config::get('app_multi_module') ? array_shift($path) : null;
1231
+ if ($autoSearch) {
1232
+ // 自动搜索控制器
1233
+ $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer');
1234
+ $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
1235
+ $item = [];
1236
+ $find = false;
1237
+ foreach ($path as $val) {
1238
+ $item[] = $val;
1239
+ $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT;
1240
+ $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT;
1241
+ if (is_file($file)) {
1242
+ $find = true;
1243
+ break;
1244
+ } else {
1245
+ $dir .= DS . Loader::parseName($val);
1246
+ }
1247
+ }
1248
+ if ($find) {
1249
+ $controller = implode('.', $item);
1250
+ $path = array_slice($path, count($item));
1251
+ } else {
1252
+ $controller = array_shift($path);
1253
+ }
1254
+ } else {
1255
+ // 解析控制器
1256
+ $controller = !empty($path) ? array_shift($path) : null;
1257
+ }
1258
+ // 解析操作
1259
+ $action = !empty($path) ? array_shift($path) : null;
1260
+ // 解析额外参数
1261
+ self::parseUrlParams(empty($path) ? '' : implode('|', $path));
1262
+ // 封装路由
1263
+ $route = [$module, $controller, $action];
1264
+ // 检查地址是否被定义过路由
1265
+ $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action);
1266
+ $name2 = '';
1267
+ if (empty($module) || isset($bind) && $module == $bind) {
1268
+ $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action);
1269
+ }
1270
+
1271
+ if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) {
1272
+ throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
1273
+ }
1274
+ }
1275
+ return ['type' => 'module', 'module' => $route];
1276
+ }
1277
+
1278
+ /**
1279
+ * 解析URL的pathinfo参数和变量
1280
+ * @access private
1281
+ * @param string $url URL地址
1282
+ * @return array
1283
+ */
1284
+ private static function parseUrlPath($url)
1285
+ {
1286
+ // 分隔符替换 确保路由定义使用统一的分隔符
1287
+ $url = str_replace('|', '/', $url);
1288
+ $url = trim($url, '/');
1289
+ $var = [];
1290
+ if (false !== strpos($url, '?')) {
1291
+ // [模块/控制器/操作?]参数1=值1&参数2=值2...
1292
+ $info = parse_url($url);
1293
+ $path = explode('/', $info['path']);
1294
+ parse_str($info['query'], $var);
1295
+ } elseif (strpos($url, '/')) {
1296
+ // [模块/控制器/操作]
1297
+ $path = explode('/', $url);
1298
+ } else {
1299
+ $path = [$url];
1300
+ }
1301
+ return [$path, $var];
1302
+ }
1303
+
1304
+ /**
1305
+ * 检测URL和规则路由是否匹配
1306
+ * @access private
1307
+ * @param string $url URL地址
1308
+ * @param string $rule 路由规则
1309
+ * @param array $pattern 变量规则
1310
+ * @return array|false
1311
+ */
1312
+ private static function match($url, $rule, $pattern)
1313
+ {
1314
+ $m2 = explode('/', $rule);
1315
+ $m1 = explode('|', $url);
1316
+
1317
+ $var = [];
1318
+ foreach ($m2 as $key => $val) {
1319
+ // val中定义了多个变量 <id><name>
1320
+ if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
1321
+ $value = [];
1322
+ $replace = [];
1323
+ foreach ($matches[1] as $name) {
1324
+ if (strpos($name, '?')) {
1325
+ $name = substr($name, 0, -1);
1326
+ $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?';
1327
+ } else {
1328
+ $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')';
1329
+ }
1330
+ $value[] = $name;
1331
+ }
1332
+ $val = str_replace($matches[0], $replace, $val);
1333
+ if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) {
1334
+ array_shift($match);
1335
+ foreach ($value as $k => $name) {
1336
+ if (isset($match[$k])) {
1337
+ $var[$name] = $match[$k];
1338
+ }
1339
+ }
1340
+ continue;
1341
+ } else {
1342
+ return false;
1343
+ }
1344
+ }
1345
+
1346
+ if (0 === strpos($val, '[:')) {
1347
+ // 可选参数
1348
+ $val = substr($val, 1, -1);
1349
+ $optional = true;
1350
+ } else {
1351
+ $optional = false;
1352
+ }
1353
+ if (0 === strpos($val, ':')) {
1354
+ // URL变量
1355
+ $name = substr($val, 1);
1356
+ if (!$optional && !isset($m1[$key])) {
1357
+ return false;
1358
+ }
1359
+ if (isset($m1[$key]) && isset($pattern[$name])) {
1360
+ // 检查变量规则
1361
+ if ($pattern[$name] instanceof \Closure) {
1362
+ $result = call_user_func_array($pattern[$name], [$m1[$key]]);
1363
+ if (false === $result) {
1364
+ return false;
1365
+ }
1366
+ } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) {
1367
+ return false;
1368
+ }
1369
+ }
1370
+ $var[$name] = isset($m1[$key]) ? $m1[$key] : '';
1371
+ } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) {
1372
+ return false;
1373
+ }
1374
+ }
1375
+ // 成功匹配后返回URL中的动态变量数组
1376
+ return $var;
1377
+ }
1378
+
1379
+ /**
1380
+ * 解析规则路由
1381
+ * @access private
1382
+ * @param string $rule 路由规则
1383
+ * @param string $route 路由地址
1384
+ * @param string $pathinfo URL地址
1385
+ * @param array $option 路由参数
1386
+ * @param array $matches 匹配的变量
1387
+ * @param bool $fromCache 通过缓存解析
1388
+ * @return array
1389
+ */
1390
+ private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false)
1391
+ {
1392
+ $request = Request::instance();
1393
+
1394
+ //保存解析缓存
1395
+ if (Config::get('route_check_cache') && !$fromCache) {
1396
+ try {
1397
+ $key = self::getCheckCacheKey($request);
1398
+ Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]);
1399
+ } catch (\Exception $e) {
1400
+
1401
+ }
1402
+ }
1403
+
1404
+ // 解析路由规则
1405
+ if ($rule) {
1406
+ $rule = explode('/', $rule);
1407
+ // 获取URL地址中的参数
1408
+ $paths = explode('|', $pathinfo);
1409
+ foreach ($rule as $item) {
1410
+ $fun = '';
1411
+ if (0 === strpos($item, '[:')) {
1412
+ $item = substr($item, 1, -1);
1413
+ }
1414
+ if (0 === strpos($item, ':')) {
1415
+ $var = substr($item, 1);
1416
+ $matches[$var] = array_shift($paths);
1417
+ } else {
1418
+ // 过滤URL中的静态变量
1419
+ array_shift($paths);
1420
+ }
1421
+ }
1422
+ } else {
1423
+ $paths = explode('|', $pathinfo);
1424
+ }
1425
+
1426
+ // 获取路由地址规则
1427
+ if (is_string($route) && isset($option['prefix'])) {
1428
+ // 路由地址前缀
1429
+ $route = $option['prefix'] . $route;
1430
+ }
1431
+ // 替换路由地址中的变量
1432
+ if (is_string($route) && !empty($matches)) {
1433
+ foreach ($matches as $key => $val) {
1434
+ if (false !== strpos($route, ':' . $key)) {
1435
+ $route = str_replace(':' . $key, $val, $route);
1436
+ }
1437
+ }
1438
+ }
1439
+
1440
+ // 绑定模型数据
1441
+ if (isset($option['bind_model'])) {
1442
+ $bind = [];
1443
+ foreach ($option['bind_model'] as $key => $val) {
1444
+ if ($val instanceof \Closure) {
1445
+ $result = call_user_func_array($val, [$matches]);
1446
+ } else {
1447
+ if (is_array($val)) {
1448
+ $fields = explode('&', $val[1]);
1449
+ $model = $val[0];
1450
+ $exception = isset($val[2]) ? $val[2] : true;
1451
+ } else {
1452
+ $fields = ['id'];
1453
+ $model = $val;
1454
+ $exception = true;
1455
+ }
1456
+ $where = [];
1457
+ $match = true;
1458
+ foreach ($fields as $field) {
1459
+ if (!isset($matches[$field])) {
1460
+ $match = false;
1461
+ break;
1462
+ } else {
1463
+ $where[$field] = $matches[$field];
1464
+ }
1465
+ }
1466
+ if ($match) {
1467
+ $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where);
1468
+ $result = $query->failException($exception)->find();
1469
+ }
1470
+ }
1471
+ if (!empty($result)) {
1472
+ $bind[$key] = $result;
1473
+ }
1474
+ }
1475
+ $request->bind($bind);
1476
+ }
1477
+
1478
+ if (!empty($option['response'])) {
1479
+ Hook::add('response_send', $option['response']);
1480
+ }
1481
+
1482
+ // 解析额外参数
1483
+ self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches);
1484
+ // 记录匹配的路由信息
1485
+ $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]);
1486
+
1487
+ // 检测路由after行为
1488
+ if (!empty($option['after_behavior'])) {
1489
+ if ($option['after_behavior'] instanceof \Closure) {
1490
+ $result = call_user_func_array($option['after_behavior'], []);
1491
+ } else {
1492
+ foreach ((array) $option['after_behavior'] as $behavior) {
1493
+ $result = Hook::exec($behavior, '');
1494
+ if (!is_null($result)) {
1495
+ break;
1496
+ }
1497
+ }
1498
+ }
1499
+ // 路由规则重定向
1500
+ if ($result instanceof Response) {
1501
+ return ['type' => 'response', 'response' => $result];
1502
+ } elseif (is_array($result)) {
1503
+ return $result;
1504
+ }
1505
+ }
1506
+
1507
+ if ($route instanceof \Closure) {
1508
+ // 执行闭包
1509
+ $result = ['type' => 'function', 'function' => $route];
1510
+ } elseif (0 === strpos($route, '/') || strpos($route, '://')) {
1511
+ // 路由到重定向地址
1512
+ $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301];
1513
+ } elseif (false !== strpos($route, '\\')) {
1514
+ // 路由到方法
1515
+ list($path, $var) = self::parseUrlPath($route);
1516
+ $route = str_replace('/', '@', implode('/', $path));
1517
+ $method = strpos($route, '@') ? explode('@', $route) : $route;
1518
+ $result = ['type' => 'method', 'method' => $method, 'var' => $var];
1519
+ } elseif (0 === strpos($route, '@')) {
1520
+ // 路由到控制器
1521
+ $route = substr($route, 1);
1522
+ list($route, $var) = self::parseUrlPath($route);
1523
+ $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var];
1524
+ $request->action(array_pop($route));
1525
+ $request->controller($route ? array_pop($route) : Config::get('default_controller'));
1526
+ $request->module($route ? array_pop($route) : Config::get('default_module'));
1527
+ App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : '');
1528
+ } else {
1529
+ // 路由到模块/控制器/操作
1530
+ $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false);
1531
+ }
1532
+ // 开启请求缓存
1533
+ if ($request->isGet() && isset($option['cache'])) {
1534
+ $cache = $option['cache'];
1535
+ if (is_array($cache)) {
1536
+ list($key, $expire, $tag) = array_pad($cache, 3, null);
1537
+ } else {
1538
+ $key = str_replace('|', '/', $pathinfo);
1539
+ $expire = $cache;
1540
+ $tag = null;
1541
+ }
1542
+ $request->cache($key, $expire, $tag);
1543
+ }
1544
+ return $result;
1545
+ }
1546
+
1547
+ /**
1548
+ * 解析URL地址为 模块/控制器/操作
1549
+ * @access private
1550
+ * @param string $url URL地址
1551
+ * @param bool $convert 是否自动转换URL地址
1552
+ * @return array
1553
+ */
1554
+ private static function parseModule($url, $convert = false)
1555
+ {
1556
+ list($path, $var) = self::parseUrlPath($url);
1557
+ $action = array_pop($path);
1558
+ $controller = !empty($path) ? array_pop($path) : null;
1559
+ $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null;
1560
+ $method = Request::instance()->method();
1561
+ if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) {
1562
+ // 操作方法前缀支持
1563
+ $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action;
1564
+ }
1565
+ // 设置当前请求的路由变量
1566
+ Request::instance()->route($var);
1567
+ // 路由到模块/控制器/操作
1568
+ return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert];
1569
+ }
1570
+
1571
+ /**
1572
+ * 解析URL地址中的参数Request对象
1573
+ * @access private
1574
+ * @param string $url 路由规则
1575
+ * @param array $var 变量
1576
+ * @return void
1577
+ */
1578
+ private static function parseUrlParams($url, &$var = [])
1579
+ {
1580
+ if ($url) {
1581
+ if (Config::get('url_param_type')) {
1582
+ $var += explode('|', $url);
1583
+ } else {
1584
+ preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
1585
+ $var[$match[1]] = strip_tags($match[2]);
1586
+ }, $url);
1587
+ }
1588
+ }
1589
+ // 设置当前请求的参数
1590
+ Request::instance()->route($var);
1591
+ }
1592
+
1593
+ // 分析路由规则中的变量
1594
+ private static function parseVar($rule)
1595
+ {
1596
+ // 提取路由规则中的变量
1597
+ $var = [];
1598
+ foreach (explode('/', $rule) as $val) {
1599
+ $optional = false;
1600
+ if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
1601
+ foreach ($matches[1] as $name) {
1602
+ if (strpos($name, '?')) {
1603
+ $name = substr($name, 0, -1);
1604
+ $optional = true;
1605
+ } else {
1606
+ $optional = false;
1607
+ }
1608
+ $var[$name] = $optional ? 2 : 1;
1609
+ }
1610
+ }
1611
+
1612
+ if (0 === strpos($val, '[:')) {
1613
+ // 可选参数
1614
+ $optional = true;
1615
+ $val = substr($val, 1, -1);
1616
+ }
1617
+ if (0 === strpos($val, ':')) {
1618
+ // URL变量
1619
+ $name = substr($val, 1);
1620
+ $var[$name] = $optional ? 2 : 1;
1621
+ }
1622
+ }
1623
+ return $var;
1624
+ }
1625
+
1626
+ /**
1627
+ * 获取路由解析缓存的key
1628
+ * @param Request $request
1629
+ * @return string
1630
+ */
1631
+ private static function getCheckCacheKey(Request $request)
1632
+ {
1633
+ static $key;
1634
+
1635
+ if (empty($key)) {
1636
+ if ($callback = Config::get('route_check_cache_key')) {
1637
+ $key = call_user_func($callback, $request);
1638
+ } else {
1639
+ $key = "{$request->host(true)}|{$request->method()}|{$request->path()}";
1640
+ }
1641
+ }
1642
+
1643
+ return $key;
1644
+ }
1645
+ }
thinkphp/library/think/Session.php ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\ClassNotFoundException;
15
+
16
+ class Session
17
+ {
18
+ protected static $prefix = '';
19
+ protected static $init = null;
20
+
21
+ /**
22
+ * 设置或者获取session作用域(前缀)
23
+ * @param string $prefix
24
+ * @return string|void
25
+ */
26
+ public static function prefix($prefix = '')
27
+ {
28
+ empty(self::$init) && self::boot();
29
+ if (empty($prefix) && null !== $prefix) {
30
+ return self::$prefix;
31
+ } else {
32
+ self::$prefix = $prefix;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * session初始化
38
+ * @param array $config
39
+ * @return void
40
+ * @throws \think\Exception
41
+ */
42
+ public static function init(array $config = [])
43
+ {
44
+ if (empty($config)) {
45
+ $config = Config::get('session');
46
+ }
47
+ // 记录初始化信息
48
+ App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info');
49
+ $isDoStart = false;
50
+ if (isset($config['use_trans_sid'])) {
51
+ ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0);
52
+ }
53
+
54
+ // 启动session
55
+ if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) {
56
+ ini_set('session.auto_start', 0);
57
+ $isDoStart = true;
58
+ }
59
+
60
+ if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) {
61
+ self::$prefix = $config['prefix'];
62
+ }
63
+ if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) {
64
+ session_id($_REQUEST[$config['var_session_id']]);
65
+ } elseif (isset($config['id']) && !empty($config['id'])) {
66
+ session_id($config['id']);
67
+ }
68
+ if (isset($config['name'])) {
69
+ session_name($config['name']);
70
+ }
71
+ if (isset($config['path'])) {
72
+ session_save_path($config['path']);
73
+ }
74
+ if (isset($config['domain'])) {
75
+ ini_set('session.cookie_domain', $config['domain']);
76
+ }
77
+ if (isset($config['expire'])) {
78
+ ini_set('session.gc_maxlifetime', $config['expire']);
79
+ ini_set('session.cookie_lifetime', $config['expire']);
80
+ }
81
+ if (isset($config['secure'])) {
82
+ ini_set('session.cookie_secure', $config['secure']);
83
+ }
84
+ if (isset($config['httponly'])) {
85
+ ini_set('session.cookie_httponly', $config['httponly']);
86
+ }
87
+ if (isset($config['use_cookies'])) {
88
+ ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0);
89
+ }
90
+ if (isset($config['cache_limiter'])) {
91
+ session_cache_limiter($config['cache_limiter']);
92
+ }
93
+ if (isset($config['cache_expire'])) {
94
+ session_cache_expire($config['cache_expire']);
95
+ }
96
+ if (!empty($config['type'])) {
97
+ // 读取session驱动
98
+ $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']);
99
+
100
+ // 检查驱动类
101
+ if (!class_exists($class) || !session_set_save_handler(new $class($config))) {
102
+ throw new ClassNotFoundException('error session handler:' . $class, $class);
103
+ }
104
+ }
105
+ if ($isDoStart) {
106
+ session_start();
107
+ self::$init = true;
108
+ } else {
109
+ self::$init = false;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * session自动启动或者初始化
115
+ * @return void
116
+ */
117
+ public static function boot()
118
+ {
119
+ if (is_null(self::$init)) {
120
+ self::init();
121
+ } elseif (false === self::$init) {
122
+ if (PHP_SESSION_ACTIVE != session_status()) {
123
+ session_start();
124
+ }
125
+ self::$init = true;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * session设置
131
+ * @param string $name session名称
132
+ * @param mixed $value session值
133
+ * @param string|null $prefix 作用域(前缀)
134
+ * @return void
135
+ */
136
+ public static function set($name, $value = '', $prefix = null)
137
+ {
138
+ empty(self::$init) && self::boot();
139
+
140
+ $prefix = !is_null($prefix) ? $prefix : self::$prefix;
141
+ if (strpos($name, '.')) {
142
+ // 二维数组赋值
143
+ list($name1, $name2) = explode('.', $name);
144
+ if ($prefix) {
145
+ $_SESSION[$prefix][$name1][$name2] = $value;
146
+ } else {
147
+ $_SESSION[$name1][$name2] = $value;
148
+ }
149
+ } elseif ($prefix) {
150
+ $_SESSION[$prefix][$name] = $value;
151
+ } else {
152
+ $_SESSION[$name] = $value;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * session获取
158
+ * @param string $name session名称
159
+ * @param string|null $prefix 作用域(前缀)
160
+ * @return mixed
161
+ */
162
+ public static function get($name = '', $prefix = null)
163
+ {
164
+ empty(self::$init) && self::boot();
165
+ $prefix = !is_null($prefix) ? $prefix : self::$prefix;
166
+ if ('' == $name) {
167
+ // 获取全部的session
168
+ $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
169
+ } elseif ($prefix) {
170
+ // 获取session
171
+ if (strpos($name, '.')) {
172
+ list($name1, $name2) = explode('.', $name);
173
+ $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null;
174
+ } else {
175
+ $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null;
176
+ }
177
+ } else {
178
+ if (strpos($name, '.')) {
179
+ list($name1, $name2) = explode('.', $name);
180
+ $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null;
181
+ } else {
182
+ $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null;
183
+ }
184
+ }
185
+ return $value;
186
+ }
187
+
188
+ /**
189
+ * session获取并删除
190
+ * @param string $name session名称
191
+ * @param string|null $prefix 作用域(前缀)
192
+ * @return mixed
193
+ */
194
+ public static function pull($name, $prefix = null)
195
+ {
196
+ $result = self::get($name, $prefix);
197
+ if ($result) {
198
+ self::delete($name, $prefix);
199
+ return $result;
200
+ } else {
201
+ return;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * session设置 下一次请求有效
207
+ * @param string $name session名称
208
+ * @param mixed $value session值
209
+ * @param string|null $prefix 作用域(前缀)
210
+ * @return void
211
+ */
212
+ public static function flash($name, $value)
213
+ {
214
+ self::set($name, $value);
215
+ if (!self::has('__flash__.__time__')) {
216
+ self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']);
217
+ }
218
+ self::push('__flash__', $name);
219
+ }
220
+
221
+ /**
222
+ * 清空当前请求的session数据
223
+ * @return void
224
+ */
225
+ public static function flush()
226
+ {
227
+ if (self::$init) {
228
+ $item = self::get('__flash__');
229
+
230
+ if (!empty($item)) {
231
+ $time = $item['__time__'];
232
+ if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) {
233
+ unset($item['__time__']);
234
+ self::delete($item);
235
+ self::set('__flash__', []);
236
+ }
237
+ }
238
+ }
239
+ }
240
+
241
+ /**
242
+ * 删除session数据
243
+ * @param string|array $name session名称
244
+ * @param string|null $prefix 作用域(前缀)
245
+ * @return void
246
+ */
247
+ public static function delete($name, $prefix = null)
248
+ {
249
+ empty(self::$init) && self::boot();
250
+ $prefix = !is_null($prefix) ? $prefix : self::$prefix;
251
+ if (is_array($name)) {
252
+ foreach ($name as $key) {
253
+ self::delete($key, $prefix);
254
+ }
255
+ } elseif (strpos($name, '.')) {
256
+ list($name1, $name2) = explode('.', $name);
257
+ if ($prefix) {
258
+ unset($_SESSION[$prefix][$name1][$name2]);
259
+ } else {
260
+ unset($_SESSION[$name1][$name2]);
261
+ }
262
+ } else {
263
+ if ($prefix) {
264
+ unset($_SESSION[$prefix][$name]);
265
+ } else {
266
+ unset($_SESSION[$name]);
267
+ }
268
+ }
269
+ }
270
+
271
+ /**
272
+ * 清空session数据
273
+ * @param string|null $prefix 作用域(前缀)
274
+ * @return void
275
+ */
276
+ public static function clear($prefix = null)
277
+ {
278
+ empty(self::$init) && self::boot();
279
+ $prefix = !is_null($prefix) ? $prefix : self::$prefix;
280
+ if ($prefix) {
281
+ unset($_SESSION[$prefix]);
282
+ } else {
283
+ $_SESSION = [];
284
+ }
285
+ }
286
+
287
+ /**
288
+ * 判断session数据
289
+ * @param string $name session名称
290
+ * @param string|null $prefix
291
+ * @return bool
292
+ */
293
+ public static function has($name, $prefix = null)
294
+ {
295
+ empty(self::$init) && self::boot();
296
+ $prefix = !is_null($prefix) ? $prefix : self::$prefix;
297
+ if (strpos($name, '.')) {
298
+ // 支持数组
299
+ list($name1, $name2) = explode('.', $name);
300
+ return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]);
301
+ } else {
302
+ return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]);
303
+ }
304
+ }
305
+
306
+ /**
307
+ * 添加数据到一个session数组
308
+ * @param string $key
309
+ * @param mixed $value
310
+ * @return void
311
+ */
312
+ public static function push($key, $value)
313
+ {
314
+ $array = self::get($key);
315
+ if (is_null($array)) {
316
+ $array = [];
317
+ }
318
+ $array[] = $value;
319
+ self::set($key, $array);
320
+ }
321
+
322
+ /**
323
+ * 启动session
324
+ * @return void
325
+ */
326
+ public static function start()
327
+ {
328
+ session_start();
329
+ self::$init = true;
330
+ }
331
+
332
+ /**
333
+ * 销毁session
334
+ * @return void
335
+ */
336
+ public static function destroy()
337
+ {
338
+ if (!empty($_SESSION)) {
339
+ $_SESSION = [];
340
+ }
341
+ session_unset();
342
+ session_destroy();
343
+ self::$init = null;
344
+ }
345
+
346
+ /**
347
+ * 重新生成session_id
348
+ * @param bool $delete 是否删除关联会话文件
349
+ * @return void
350
+ */
351
+ public static function regenerate($delete = false)
352
+ {
353
+ session_regenerate_id($delete);
354
+ }
355
+
356
+ /**
357
+ * 暂停session
358
+ * @return void
359
+ */
360
+ public static function pause()
361
+ {
362
+ // 暂停session
363
+ session_write_close();
364
+ self::$init = false;
365
+ }
366
+ }
thinkphp/library/think/Template.php ADDED
@@ -0,0 +1,1139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\TemplateNotFoundException;
15
+ use think\template\TagLib;
16
+
17
+ /**
18
+ * ThinkPHP分离出来的模板引擎
19
+ * 支持XML标签和普通标签的模板解析
20
+ * 编译型模板引擎 支持动态缓存
21
+ */
22
+ class Template
23
+ {
24
+ // 模板变量
25
+ protected $data = [];
26
+ // 引擎配置
27
+ protected $config = [
28
+ 'view_path' => '', // 模板路径
29
+ 'view_base' => '',
30
+ 'view_suffix' => 'html', // 默认模板文件后缀
31
+ 'view_depr' => DS,
32
+ 'cache_suffix' => 'php', // 默认模板缓存后缀
33
+ 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数
34
+ 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码
35
+ 'tpl_begin' => '{', // 模板引擎普通标签开始标记
36
+ 'tpl_end' => '}', // 模板引擎普通标签结束标记
37
+ 'strip_space' => false, // 是否去除模板文件里面的html空格与换行
38
+ 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译
39
+ 'compile_type' => 'file', // 模板编译类型
40
+ 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变
41
+ 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒)
42
+ 'layout_on' => false, // 布局模板开关
43
+ 'layout_name' => 'layout', // 布局模板入口文件
44
+ 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识
45
+ 'taglib_begin' => '{', // 标签库标签开始标记
46
+ 'taglib_end' => '}', // 标签库标签结束标记
47
+ 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测
48
+ 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序
49
+ 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔
50
+ 'display_cache' => false, // 模板渲染缓存
51
+ 'cache_id' => '', // 模板缓存ID
52
+ 'tpl_replace_string' => [],
53
+ 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别
54
+ ];
55
+
56
+ private $literal = [];
57
+ private $includeFile = []; // 记录所有模板包含的文件路径及更新时间
58
+ protected $storage;
59
+
60
+ /**
61
+ * 构造函数
62
+ * @access public
63
+ * @param array $config
64
+ */
65
+ public function __construct(array $config = [])
66
+ {
67
+ $this->config['cache_path'] = TEMP_PATH;
68
+ $this->config = array_merge($this->config, $config);
69
+
70
+ $this->config['taglib_begin_origin'] = $this->config['taglib_begin'];
71
+ $this->config['taglib_end_origin'] = $this->config['taglib_end'];
72
+
73
+ $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/');
74
+ $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/');
75
+ $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/');
76
+ $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/');
77
+
78
+ // 初始化模板编译存储器
79
+ $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
80
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type);
81
+ $this->storage = new $class();
82
+ }
83
+
84
+ /**
85
+ * 模板变量赋值
86
+ * @access public
87
+ * @param mixed $name
88
+ * @param mixed $value
89
+ * @return void
90
+ */
91
+ public function assign($name, $value = '')
92
+ {
93
+ if (is_array($name)) {
94
+ $this->data = array_merge($this->data, $name);
95
+ } else {
96
+ $this->data[$name] = $value;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 模板引擎参数赋值
102
+ * @access public
103
+ * @param mixed $name
104
+ * @param mixed $value
105
+ */
106
+ public function __set($name, $value)
107
+ {
108
+ $this->config[$name] = $value;
109
+ }
110
+
111
+ /**
112
+ * 模板引擎配置项
113
+ * @access public
114
+ * @param array|string $config
115
+ * @return string|void|array
116
+ */
117
+ public function config($config)
118
+ {
119
+ if (is_array($config)) {
120
+ $this->config = array_merge($this->config, $config);
121
+ } elseif (isset($this->config[$config])) {
122
+ return $this->config[$config];
123
+ } else {
124
+ return;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * 模板变量获取
130
+ * @access public
131
+ * @param string $name 变量名
132
+ * @return mixed
133
+ */
134
+ public function get($name = '')
135
+ {
136
+ if ('' == $name) {
137
+ return $this->data;
138
+ } else {
139
+ $data = $this->data;
140
+ foreach (explode('.', $name) as $key => $val) {
141
+ if (isset($data[$val])) {
142
+ $data = $data[$val];
143
+ } else {
144
+ $data = null;
145
+ break;
146
+ }
147
+ }
148
+ return $data;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * 渲染模板文件
154
+ * @access public
155
+ * @param string $template 模板文件
156
+ * @param array $vars 模板变量
157
+ * @param array $config 模板参数
158
+ * @return void
159
+ */
160
+ public function fetch($template, $vars = [], $config = [])
161
+ {
162
+ if ($vars) {
163
+ $this->data = $vars;
164
+ }
165
+ if ($config) {
166
+ $this->config($config);
167
+ }
168
+ if (!empty($this->config['cache_id']) && $this->config['display_cache']) {
169
+ // 读取渲染缓存
170
+ $cacheContent = Cache::get($this->config['cache_id']);
171
+ if (false !== $cacheContent) {
172
+ echo $cacheContent;
173
+ return;
174
+ }
175
+ }
176
+ $template = $this->parseTemplateFile($template);
177
+ if ($template) {
178
+ $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.');
179
+ if (!$this->checkCache($cacheFile)) {
180
+ // 缓存无效 重新模板编译
181
+ $content = file_get_contents($template);
182
+ $this->compiler($content, $cacheFile);
183
+ }
184
+ // 页面缓存
185
+ ob_start();
186
+ ob_implicit_flush(0);
187
+ // 读取编译存储
188
+ $this->storage->read($cacheFile, $this->data);
189
+ // 获取并清空缓存
190
+ $content = ob_get_clean();
191
+ if (!empty($this->config['cache_id']) && $this->config['display_cache']) {
192
+ // 缓存页面输出
193
+ Cache::set($this->config['cache_id'], $content, $this->config['cache_time']);
194
+ }
195
+ echo $content;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 渲染模板内容
201
+ * @access public
202
+ * @param string $content 模板内容
203
+ * @param array $vars 模板变量
204
+ * @param array $config 模板参数
205
+ * @return void
206
+ */
207
+ public function display($content, $vars = [], $config = [])
208
+ {
209
+ if ($vars) {
210
+ $this->data = $vars;
211
+ }
212
+ if ($config) {
213
+ $this->config($config);
214
+ }
215
+ $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.');
216
+ if (!$this->checkCache($cacheFile)) {
217
+ // 缓存无效 模板编译
218
+ $this->compiler($content, $cacheFile);
219
+ }
220
+ // 读取编译存储
221
+ $this->storage->read($cacheFile, $this->data);
222
+ }
223
+
224
+ /**
225
+ * 设置布局
226
+ * @access public
227
+ * @param mixed $name 布局模板名称 false 则关闭布局
228
+ * @param string $replace 布局模板内容替换标识
229
+ * @return Template
230
+ */
231
+ public function layout($name, $replace = '')
232
+ {
233
+ if (false === $name) {
234
+ // 关闭布局
235
+ $this->config['layout_on'] = false;
236
+ } else {
237
+ // 开启布局
238
+ $this->config['layout_on'] = true;
239
+ // 名称必须为字符串
240
+ if (is_string($name)) {
241
+ $this->config['layout_name'] = $name;
242
+ }
243
+ if (!empty($replace)) {
244
+ $this->config['layout_item'] = $replace;
245
+ }
246
+ }
247
+ return $this;
248
+ }
249
+
250
+ /**
251
+ * 检查编译缓存是否有效
252
+ * 如果无效则需要重新编译
253
+ * @access private
254
+ * @param string $cacheFile 缓存文件名
255
+ * @return boolean
256
+ */
257
+ private function checkCache($cacheFile)
258
+ {
259
+ // 未开启缓存功能
260
+ if (!$this->config['tpl_cache']) {
261
+ return false;
262
+ }
263
+ // 缓存文件不存在
264
+ if (!is_file($cacheFile)) {
265
+ return false;
266
+ }
267
+ // 读取缓存文件失败
268
+ if (!$handle = @fopen($cacheFile, "r")) {
269
+ return false;
270
+ }
271
+ // 读取第一行
272
+ preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches);
273
+ if (!isset($matches[1])) {
274
+ return false;
275
+ }
276
+ $includeFile = unserialize($matches[1]);
277
+ if (!is_array($includeFile)) {
278
+ return false;
279
+ }
280
+ // 检查模板文件是否有更新
281
+ foreach ($includeFile as $path => $time) {
282
+ if (is_file($path) && filemtime($path) > $time) {
283
+ // 模板文件如果有更新则缓存需要更新
284
+ return false;
285
+ }
286
+ }
287
+ // 检查编译存储是否有效
288
+ return $this->storage->check($cacheFile, $this->config['cache_time']);
289
+ }
290
+
291
+ /**
292
+ * 检查编译缓存是否存在
293
+ * @access public
294
+ * @param string $cacheId 缓存的id
295
+ * @return boolean
296
+ */
297
+ public function isCache($cacheId)
298
+ {
299
+ if ($cacheId && $this->config['display_cache']) {
300
+ // 缓存页面输出
301
+ return Cache::has($cacheId);
302
+ }
303
+ return false;
304
+ }
305
+
306
+ /**
307
+ * 编译模板文件内容
308
+ * @access private
309
+ * @param string $content 模板内容
310
+ * @param string $cacheFile 缓存文件名
311
+ * @return void
312
+ */
313
+ private function compiler(&$content, $cacheFile)
314
+ {
315
+ // 判断是否启用布局
316
+ if ($this->config['layout_on']) {
317
+ if (false !== strpos($content, '{__NOLAYOUT__}')) {
318
+ // 可以单独定义不使用布局
319
+ $content = str_replace('{__NOLAYOUT__}', '', $content);
320
+ } else {
321
+ // 读取布局模板
322
+ $layoutFile = $this->parseTemplateFile($this->config['layout_name']);
323
+ if ($layoutFile) {
324
+ // 替换布局的主体内容
325
+ $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile));
326
+ }
327
+ }
328
+ } else {
329
+ $content = str_replace('{__NOLAYOUT__}', '', $content);
330
+ }
331
+
332
+ // 模板解析
333
+ $this->parse($content);
334
+ if ($this->config['strip_space']) {
335
+ /* 去除html空格与换行 */
336
+ $find = ['~>\s+<~', '~>(\s+\n|\r)~'];
337
+ $replace = ['><', '>'];
338
+ $content = preg_replace($find, $replace, $content);
339
+ }
340
+ // 优化生成的php代码
341
+ $content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content);
342
+ // 模板过滤输出
343
+ $replace = $this->config['tpl_replace_string'];
344
+ $content = str_replace(array_keys($replace), array_values($replace), $content);
345
+ // 添加安全代码及模板引用记录
346
+ $content = '<?php if (!defined(\'THINK_PATH\')) exit(); /*' . serialize($this->includeFile) . '*/ ?>' . "\n" . $content;
347
+ // 编译存储
348
+ $this->storage->write($cacheFile, $content);
349
+ $this->includeFile = [];
350
+ return;
351
+ }
352
+
353
+ /**
354
+ * 模板解析入口
355
+ * 支持普通标签和TagLib解析 支持自定义标签库
356
+ * @access public
357
+ * @param string $content 要解析的模板内容
358
+ * @return void
359
+ */
360
+ public function parse(&$content)
361
+ {
362
+ // 内容为空不解析
363
+ if (empty($content)) {
364
+ return;
365
+ }
366
+ // 替换literal标签内容
367
+ $this->parseLiteral($content);
368
+ // 解析继承
369
+ $this->parseExtend($content);
370
+ // 解析布局
371
+ $this->parseLayout($content);
372
+ // 检查include语法
373
+ $this->parseInclude($content);
374
+ // 替换包含文件中literal标签内容
375
+ $this->parseLiteral($content);
376
+ // 检查PHP语法
377
+ $this->parsePhp($content);
378
+
379
+ // 获取需要引入的标签库列表
380
+ // 标签库只需要定义一次,允许引入多个一次
381
+ // 一般放在文件的最前面
382
+ // 格式:<taglib name="html,mytag..." />
383
+ // 当TAGLIB_LOAD配置为true时才会进行检测
384
+ if ($this->config['taglib_load']) {
385
+ $tagLibs = $this->getIncludeTagLib($content);
386
+ if (!empty($tagLibs)) {
387
+ // 对导入的TagLib进行解析
388
+ foreach ($tagLibs as $tagLibName) {
389
+ $this->parseTagLib($tagLibName, $content);
390
+ }
391
+ }
392
+ }
393
+ // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀
394
+ if ($this->config['taglib_pre_load']) {
395
+ $tagLibs = explode(',', $this->config['taglib_pre_load']);
396
+ foreach ($tagLibs as $tag) {
397
+ $this->parseTagLib($tag, $content);
398
+ }
399
+ }
400
+ // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀
401
+ $tagLibs = explode(',', $this->config['taglib_build_in']);
402
+ foreach ($tagLibs as $tag) {
403
+ $this->parseTagLib($tag, $content, true);
404
+ }
405
+ // 解析普通模板标签 {$tagName}
406
+ $this->parseTag($content);
407
+
408
+ // 还原被替换的Literal标签
409
+ $this->parseLiteral($content, true);
410
+ return;
411
+ }
412
+
413
+ /**
414
+ * 检查PHP语法
415
+ * @access private
416
+ * @param string $content 要解析的模板内容
417
+ * @return void
418
+ * @throws \think\Exception
419
+ */
420
+ private function parsePhp(&$content)
421
+ {
422
+ // 短标签的情况要将<?标签用echo方式输出 否则无法正常输出xml标识
423
+ $content = preg_replace('/(<\?(?!php|=|$))/i', '<?php echo \'\\1\'; ?>' . "\n", $content);
424
+ // PHP语法检查
425
+ if ($this->config['tpl_deny_php'] && false !== strpos($content, '<?php')) {
426
+ throw new Exception('not allow php tag', 11600);
427
+ }
428
+ return;
429
+ }
430
+
431
+ /**
432
+ * 解析模板中的布局标签
433
+ * @access private
434
+ * @param string $content 要解析的模板内容
435
+ * @return void
436
+ */
437
+ private function parseLayout(&$content)
438
+ {
439
+ // 读取模板中的布局标签
440
+ if (preg_match($this->getRegex('layout'), $content, $matches)) {
441
+ // 替换Layout标签
442
+ $content = str_replace($matches[0], '', $content);
443
+ // 解析Layout标签
444
+ $array = $this->parseAttr($matches[0]);
445
+ if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) {
446
+ // 读取布局模板
447
+ $layoutFile = $this->parseTemplateFile($array['name']);
448
+ if ($layoutFile) {
449
+ $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item'];
450
+ // 替换布局的主体内容
451
+ $content = str_replace($replace, $content, file_get_contents($layoutFile));
452
+ }
453
+ }
454
+ } else {
455
+ $content = str_replace('{__NOLAYOUT__}', '', $content);
456
+ }
457
+ return;
458
+ }
459
+
460
+ /**
461
+ * 解析模板中的include标签
462
+ * @access private
463
+ * @param string $content 要解析的模板内容
464
+ * @return void
465
+ */
466
+ private function parseInclude(&$content)
467
+ {
468
+ $regex = $this->getRegex('include');
469
+ $func = function ($template) use (&$func, &$regex, &$content) {
470
+ if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) {
471
+ foreach ($matches as $match) {
472
+ $array = $this->parseAttr($match[0]);
473
+ $file = $array['file'];
474
+ unset($array['file']);
475
+ // 分析模板文件名并读取内容
476
+ $parseStr = $this->parseTemplateName($file);
477
+ foreach ($array as $k => $v) {
478
+ // 以$开头字符串转换成模板变量
479
+ if (0 === strpos($v, '$')) {
480
+ $v = $this->get(substr($v, 1));
481
+ }
482
+ $parseStr = str_replace('[' . $k . ']', $v, $parseStr);
483
+ }
484
+ $content = str_replace($match[0], $parseStr, $content);
485
+ // 再次对包含文件进行模板分析
486
+ $func($parseStr);
487
+ }
488
+ unset($matches);
489
+ }
490
+ };
491
+ // 替换模板中的include标签
492
+ $func($content);
493
+ return;
494
+ }
495
+
496
+ /**
497
+ * 解析模板中的extend标签
498
+ * @access private
499
+ * @param string $content 要解析的模板内容
500
+ * @return void
501
+ */
502
+ private function parseExtend(&$content)
503
+ {
504
+ $regex = $this->getRegex('extend');
505
+ $array = $blocks = $baseBlocks = [];
506
+ $extend = '';
507
+ $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) {
508
+ if (preg_match($regex, $template, $matches)) {
509
+ if (!isset($array[$matches['name']])) {
510
+ $array[$matches['name']] = 1;
511
+ // 读取继承模板
512
+ $extend = $this->parseTemplateName($matches['name']);
513
+ // 递归检查继承
514
+ $func($extend);
515
+ // 取得block标签内容
516
+ $blocks = array_merge($blocks, $this->parseBlock($template));
517
+ return;
518
+ }
519
+ } else {
520
+ // 取得顶层模板block标签内容
521
+ $baseBlocks = $this->parseBlock($template, true);
522
+ if (empty($extend)) {
523
+ // 无extend标签但有block标签的情况
524
+ $extend = $template;
525
+ }
526
+ }
527
+ };
528
+
529
+ $func($content);
530
+ if (!empty($extend)) {
531
+ if ($baseBlocks) {
532
+ $children = [];
533
+ foreach ($baseBlocks as $name => $val) {
534
+ $replace = $val['content'];
535
+ if (!empty($children[$name])) {
536
+ // 如果包含有子block标签
537
+ foreach ($children[$name] as $key) {
538
+ $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace);
539
+ }
540
+ }
541
+ if (isset($blocks[$name])) {
542
+ // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖
543
+ $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']);
544
+ if (!empty($val['parent'])) {
545
+ // 如果不是最顶层的block标签
546
+ $parent = $val['parent'];
547
+ if (isset($blocks[$parent])) {
548
+ $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']);
549
+ }
550
+ $blocks[$name]['content'] = $replace;
551
+ $children[$parent][] = $name;
552
+ continue;
553
+ }
554
+ } elseif (!empty($val['parent'])) {
555
+ // 如果子标签没有被继承则用原值
556
+ $children[$val['parent']][] = $name;
557
+ $blocks[$name] = $val;
558
+ }
559
+ if (!$val['parent']) {
560
+ // 替换模板中的顶级block标签
561
+ $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend);
562
+ }
563
+ }
564
+ }
565
+ $content = $extend;
566
+ unset($blocks, $baseBlocks);
567
+ }
568
+ return;
569
+ }
570
+
571
+ /**
572
+ * 替换页面中的literal标签
573
+ * @access private
574
+ * @param string $content 模板内容
575
+ * @param boolean $restore 是否为还原
576
+ * @return void
577
+ */
578
+ private function parseLiteral(&$content, $restore = false)
579
+ {
580
+ $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal');
581
+ if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
582
+ if (!$restore) {
583
+ $count = count($this->literal);
584
+ // 替换literal标签
585
+ foreach ($matches as $match) {
586
+ $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2]));
587
+ $content = str_replace($match[0], "<!--###literal{$count}###-->", $content);
588
+ $count++;
589
+ }
590
+ } else {
591
+ // 还原literal标签
592
+ foreach ($matches as $match) {
593
+ $content = str_replace($match[0], $this->literal[$match[1]], $content);
594
+ }
595
+ // 清空literal记录
596
+ $this->literal = [];
597
+ }
598
+ unset($matches);
599
+ }
600
+ return;
601
+ }
602
+
603
+ /**
604
+ * 获取模板中的block标签
605
+ * @access private
606
+ * @param string $content 模板内容
607
+ * @param boolean $sort 是否排序
608
+ * @return array
609
+ */
610
+ private function parseBlock(&$content, $sort = false)
611
+ {
612
+ $regex = $this->getRegex('block');
613
+ $result = [];
614
+ if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
615
+ $right = $keys = [];
616
+ foreach ($matches as $match) {
617
+ if (empty($match['name'][0])) {
618
+ if (count($right) > 0) {
619
+ $tag = array_pop($right);
620
+ $start = $tag['offset'] + strlen($tag['tag']);
621
+ $length = $match[0][1] - $start;
622
+ $result[$tag['name']] = [
623
+ 'begin' => $tag['tag'],
624
+ 'content' => substr($content, $start, $length),
625
+ 'end' => $match[0][0],
626
+ 'parent' => count($right) ? end($right)['name'] : '',
627
+ ];
628
+ $keys[$tag['name']] = $match[0][1];
629
+ }
630
+ } else {
631
+ // 标签头压入栈
632
+ $right[] = [
633
+ 'name' => $match[2][0],
634
+ 'offset' => $match[0][1],
635
+ 'tag' => $match[0][0],
636
+ ];
637
+ }
638
+ }
639
+ unset($right, $matches);
640
+ if ($sort) {
641
+ // 按block标签结束符在模板中的位置排序
642
+ array_multisort($keys, $result);
643
+ }
644
+ }
645
+ return $result;
646
+ }
647
+
648
+ /**
649
+ * 搜索模板页面中包含的TagLib库
650
+ * 并返回列表
651
+ * @access private
652
+ * @param string $content 模板内容
653
+ * @return array|null
654
+ */
655
+ private function getIncludeTagLib(&$content)
656
+ {
657
+ // 搜索是否有TagLib标签
658
+ if (preg_match($this->getRegex('taglib'), $content, $matches)) {
659
+ // 替换TagLib标签
660
+ $content = str_replace($matches[0], '', $content);
661
+ return explode(',', $matches['name']);
662
+ }
663
+ return;
664
+ }
665
+
666
+ /**
667
+ * TagLib库解析
668
+ * @access public
669
+ * @param string $tagLib 要解析的标签库
670
+ * @param string $content 要解析的模板内容
671
+ * @param boolean $hide 是否隐藏标签库前缀
672
+ * @return void
673
+ */
674
+ public function parseTagLib($tagLib, &$content, $hide = false)
675
+ {
676
+ if (false !== strpos($tagLib, '\\')) {
677
+ // 支持指定标签库的命名空间
678
+ $className = $tagLib;
679
+ $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1);
680
+ } else {
681
+ $className = '\\think\\template\\taglib\\' . ucwords($tagLib);
682
+ }
683
+ /** @var Taglib $tLib */
684
+ $tLib = new $className($this);
685
+ $tLib->parseTag($content, $hide ? '' : $tagLib);
686
+ return;
687
+ }
688
+
689
+ /**
690
+ * 分析标签属性
691
+ * @access public
692
+ * @param string $str 属性字符串
693
+ * @param string $name 不为空时返回指定的属性名
694
+ * @return array
695
+ */
696
+ public function parseAttr($str, $name = null)
697
+ {
698
+ $regex = '/\s+(?>(?P<name>[\w-]+)\s*)=(?>\s*)([\"\'])(?P<value>(?:(?!\\2).)*)\\2/is';
699
+ $array = [];
700
+ if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) {
701
+ foreach ($matches as $match) {
702
+ $array[$match['name']] = $match['value'];
703
+ }
704
+ unset($matches);
705
+ }
706
+ if (!empty($name) && isset($array[$name])) {
707
+ return $array[$name];
708
+ } else {
709
+ return $array;
710
+ }
711
+ }
712
+
713
+ /**
714
+ * 模板标签解析
715
+ * 格式: {TagName:args [|content] }
716
+ * @access private
717
+ * @param string $content 要解析的模板内容
718
+ * @return void
719
+ */
720
+ private function parseTag(&$content)
721
+ {
722
+ $regex = $this->getRegex('tag');
723
+ if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
724
+ foreach ($matches as $match) {
725
+ $str = stripslashes($match[1]);
726
+ $flag = substr($str, 0, 1);
727
+ switch ($flag) {
728
+ case '$':
729
+ // 解析模板变量 格式 {$varName}
730
+ // 是否带有?号
731
+ if (false !== $pos = strpos($str, '?')) {
732
+ $array = preg_split('/([!=]={1,2}|(?<!-)[><]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE);
733
+ $name = $array[0];
734
+ $this->parseVar($name);
735
+ $this->parseVarFunction($name);
736
+
737
+ $str = trim(substr($str, $pos + 1));
738
+ $this->parseVar($str);
739
+ $first = substr($str, 0, 1);
740
+ if (strpos($name, ')')) {
741
+ // $name为对象或是自动识别,或者含有函数
742
+ if (isset($array[1])) {
743
+ $this->parseVar($array[2]);
744
+ $name .= $array[1] . $array[2];
745
+ }
746
+ switch ($first) {
747
+ case '?':
748
+ $str = '<?php echo (' . $name . ') ? ' . $name . ' : ' . substr($str, 1) . '; ?>';
749
+ break;
750
+ case '=':
751
+ $str = '<?php if(' . $name . ') echo ' . substr($str, 1) . '; ?>';
752
+ break;
753
+ default:
754
+ $str = '<?php echo ' . $name . '?' . $str . '; ?>';
755
+ }
756
+ } else {
757
+ if (isset($array[1])) {
758
+ $this->parseVar($array[2]);
759
+ $express = $name . $array[1] . $array[2];
760
+ } else {
761
+ $express = false;
762
+ }
763
+ // $name为数组
764
+ switch ($first) {
765
+ case '?':
766
+ // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx
767
+ $str = '<?php echo ' . ($express ?: 'isset(' . $name . ')') . '?' . $name . ':' . substr($str, 1) . '; ?>';
768
+ break;
769
+ case '=':
770
+ // {$varname?='xxx'} $varname为真时才输出xxx
771
+ $str = '<?php if(' . ($express ?: '!empty(' . $name . ')') . ') echo ' . substr($str, 1) . '; ?>';
772
+ break;
773
+ case ':':
774
+ // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx
775
+ $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . '?' . $name . $str . '; ?>';
776
+ break;
777
+ default:
778
+ $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . '?' . $str . '; ?>';
779
+ }
780
+ }
781
+ } else {
782
+ $this->parseVar($str);
783
+ $this->parseVarFunction($str);
784
+ $str = '<?php echo ' . $str . '; ?>';
785
+ }
786
+ break;
787
+ case ':':
788
+ // 输出某个函数的结果
789
+ $str = substr($str, 1);
790
+ $this->parseVar($str);
791
+ $str = '<?php echo ' . $str . '; ?>';
792
+ break;
793
+ case '~':
794
+ // 执行某个函数
795
+ $str = substr($str, 1);
796
+ $this->parseVar($str);
797
+ $str = '<?php ' . $str . '; ?>';
798
+ break;
799
+ case '-':
800
+ case '+':
801
+ // 输出计算
802
+ $this->parseVar($str);
803
+ $str = '<?php echo ' . $str . '; ?>';
804
+ break;
805
+ case '/':
806
+ // 注释标签
807
+ $flag2 = substr($str, 1, 1);
808
+ if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) {
809
+ $str = '';
810
+ }
811
+ break;
812
+ default:
813
+ // 未识别的标签直接返回
814
+ $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end'];
815
+ break;
816
+ }
817
+ $content = str_replace($match[0], $str, $content);
818
+ }
819
+ unset($matches);
820
+ }
821
+ return;
822
+ }
823
+
824
+ /**
825
+ * 模板变量解析,支持使用函数
826
+ * 格式: {$varname|function1|function2=arg1,arg2}
827
+ * @access public
828
+ * @param string $varStr 变量数据
829
+ * @return void
830
+ */
831
+ public function parseVar(&$varStr)
832
+ {
833
+ $varStr = trim($varStr);
834
+ if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) {
835
+ static $_varParseList = [];
836
+ while ($matches[0]) {
837
+ $match = array_pop($matches[0]);
838
+ //如果已经解析过该变量字串,则直接返回变量值
839
+ if (isset($_varParseList[$match[0]])) {
840
+ $parseStr = $_varParseList[$match[0]];
841
+ } else {
842
+ if (strpos($match[0], '.')) {
843
+ $vars = explode('.', $match[0]);
844
+ $first = array_shift($vars);
845
+ if ('$Think' == $first) {
846
+ // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出
847
+ $parseStr = $this->parseThinkVar($vars);
848
+ } elseif ('$Request' == $first) {
849
+ // 获取Request请求对象参数
850
+ $method = array_shift($vars);
851
+ if (!empty($vars)) {
852
+ $params = implode('.', $vars);
853
+ if ('true' != $params) {
854
+ $params = '\'' . $params . '\'';
855
+ }
856
+ } else {
857
+ $params = '';
858
+ }
859
+ $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')';
860
+ } else {
861
+ switch ($this->config['tpl_var_identify']) {
862
+ case 'array': // 识别为数组
863
+ $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']';
864
+ break;
865
+ case 'obj': // 识别为对象
866
+ $parseStr = $first . '->' . implode('->', $vars);
867
+ break;
868
+ default: // 自动判断数组或对象
869
+ $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')';
870
+ }
871
+ }
872
+ } else {
873
+ $parseStr = str_replace(':', '->', $match[0]);
874
+ }
875
+ $_varParseList[$match[0]] = $parseStr;
876
+ }
877
+ $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0]));
878
+ }
879
+ unset($matches);
880
+ }
881
+ return;
882
+ }
883
+
884
+ /**
885
+ * 对模板中使用了函数的变量进行解析
886
+ * 格式 {$varname|function1|function2=arg1,arg2}
887
+ * @access public
888
+ * @param string $varStr 变量字符串
889
+ * @return void
890
+ */
891
+ public function parseVarFunction(&$varStr)
892
+ {
893
+ if (false == strpos($varStr, '|')) {
894
+ return;
895
+ }
896
+ static $_varFunctionList = [];
897
+ $_key = md5($varStr);
898
+ //如果已经解析过该变量字串,则直接返回变量值
899
+ if (isset($_varFunctionList[$_key])) {
900
+ $varStr = $_varFunctionList[$_key];
901
+ } else {
902
+ $varArray = explode('|', $varStr);
903
+ // 取得变量名称
904
+ $name = array_shift($varArray);
905
+ // 对变量使用函数
906
+ $length = count($varArray);
907
+ // 取得模板禁止使用函数列表
908
+ $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']);
909
+ for ($i = 0; $i < $length; $i++) {
910
+ $args = explode('=', $varArray[$i], 2);
911
+ // 模板函数过滤
912
+ $fun = trim($args[0]);
913
+ switch ($fun) {
914
+ case 'default': // 特殊模板函数
915
+ if (false === strpos($name, '(')) {
916
+ $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')';
917
+ } else {
918
+ $name = '(' . $name . ' ?: ' . $args[1] . ')';
919
+ }
920
+ break;
921
+ default: // 通用模板函数
922
+ if (!in_array($fun, $template_deny_funs)) {
923
+ if (isset($args[1])) {
924
+ if (strstr($args[1], '###')) {
925
+ $args[1] = str_replace('###', $name, $args[1]);
926
+ $name = "$fun($args[1])";
927
+ } else {
928
+ $name = "$fun($name,$args[1])";
929
+ }
930
+ } else {
931
+ if (!empty($args[0])) {
932
+ $name = "$fun($name)";
933
+ }
934
+ }
935
+ }
936
+ }
937
+ }
938
+ $_varFunctionList[$_key] = $name;
939
+ $varStr = $name;
940
+ }
941
+ return;
942
+ }
943
+
944
+ /**
945
+ * 特殊模板变量解析
946
+ * 格式 以 $Think. 打头的变量属于特殊模板变量
947
+ * @access public
948
+ * @param array $vars 变量数组
949
+ * @return string
950
+ */
951
+ public function parseThinkVar($vars)
952
+ {
953
+ $type = strtoupper(trim(array_shift($vars)));
954
+ $param = implode('.', $vars);
955
+ if ($vars) {
956
+ switch ($type) {
957
+ case 'SERVER':
958
+ $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')';
959
+ break;
960
+ case 'GET':
961
+ $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')';
962
+ break;
963
+ case 'POST':
964
+ $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')';
965
+ break;
966
+ case 'COOKIE':
967
+ $parseStr = '\\think\\Cookie::get(\'' . $param . '\')';
968
+ break;
969
+ case 'SESSION':
970
+ $parseStr = '\\think\\Session::get(\'' . $param . '\')';
971
+ break;
972
+ case 'ENV':
973
+ $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')';
974
+ break;
975
+ case 'REQUEST':
976
+ $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')';
977
+ break;
978
+ case 'CONST':
979
+ $parseStr = strtoupper($param);
980
+ break;
981
+ case 'LANG':
982
+ $parseStr = '\\think\\Lang::get(\'' . $param . '\')';
983
+ break;
984
+ case 'CONFIG':
985
+ $parseStr = '\\think\\Config::get(\'' . $param . '\')';
986
+ break;
987
+ default:
988
+ $parseStr = '\'\'';
989
+ break;
990
+ }
991
+ } else {
992
+ switch ($type) {
993
+ case 'NOW':
994
+ $parseStr = "date('Y-m-d g:i a',time())";
995
+ break;
996
+ case 'VERSION':
997
+ $parseStr = 'THINK_VERSION';
998
+ break;
999
+ case 'LDELIM':
1000
+ $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\'';
1001
+ break;
1002
+ case 'RDELIM':
1003
+ $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\'';
1004
+ break;
1005
+ default:
1006
+ if (defined($type)) {
1007
+ $parseStr = $type;
1008
+ } else {
1009
+ $parseStr = '';
1010
+ }
1011
+ }
1012
+ }
1013
+ return $parseStr;
1014
+ }
1015
+
1016
+ /**
1017
+ * 分析加载的模板文件并读取内容 支持多个模板文件读取
1018
+ * @access private
1019
+ * @param string $templateName 模板文件名
1020
+ * @return string
1021
+ */
1022
+ private function parseTemplateName($templateName)
1023
+ {
1024
+ $array = explode(',', $templateName);
1025
+ $parseStr = '';
1026
+ foreach ($array as $templateName) {
1027
+ if (empty($templateName)) {
1028
+ continue;
1029
+ }
1030
+ if (0 === strpos($templateName, '$')) {
1031
+ //支持加载变量文件名
1032
+ $templateName = $this->get(substr($templateName, 1));
1033
+ }
1034
+ $template = $this->parseTemplateFile($templateName);
1035
+ if ($template) {
1036
+ // 获取模板文件内容
1037
+ $parseStr .= file_get_contents($template);
1038
+ }
1039
+ }
1040
+ return $parseStr;
1041
+ }
1042
+
1043
+ /**
1044
+ * 解析模板文件名
1045
+ * @access private
1046
+ * @param string $template 文件名
1047
+ * @return string|false
1048
+ */
1049
+ private function parseTemplateFile($template)
1050
+ {
1051
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
1052
+ if (strpos($template, '@')) {
1053
+ list($module, $template) = explode('@', $template);
1054
+ }
1055
+ if (0 !== strpos($template, '/')) {
1056
+ $template = str_replace(['/', ':'], $this->config['view_depr'], $template);
1057
+ } else {
1058
+ $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1));
1059
+ }
1060
+ if ($this->config['view_base']) {
1061
+ $module = isset($module) ? $module : Request::instance()->module();
1062
+ $path = $this->config['view_base'] . ($module ? $module . DS : '');
1063
+ } else {
1064
+ $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path'];
1065
+ }
1066
+ $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.'));
1067
+ }
1068
+
1069
+ if (is_file($template)) {
1070
+ // 记录模板文件的更新时间
1071
+ $this->includeFile[$template] = filemtime($template);
1072
+ return $template;
1073
+ } else {
1074
+ throw new TemplateNotFoundException('template not exists:' . $template, $template);
1075
+ }
1076
+ }
1077
+
1078
+ /**
1079
+ * 按标签生成正则
1080
+ * @access private
1081
+ * @param string $tagName 标签名
1082
+ * @return string
1083
+ */
1084
+ private function getRegex($tagName)
1085
+ {
1086
+ $regex = '';
1087
+ if ('tag' == $tagName) {
1088
+ $begin = $this->config['tpl_begin'];
1089
+ $end = $this->config['tpl_end'];
1090
+ if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) {
1091
+ $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end;
1092
+ } else {
1093
+ $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end;
1094
+ }
1095
+ } else {
1096
+ $begin = $this->config['taglib_begin'];
1097
+ $end = $this->config['taglib_end'];
1098
+ $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;
1099
+ switch ($tagName) {
1100
+ case 'block':
1101
+ if ($single) {
1102
+ $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end;
1103
+ } else {
1104
+ $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end;
1105
+ }
1106
+ break;
1107
+ case 'literal':
1108
+ if ($single) {
1109
+ $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')';
1110
+ $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)';
1111
+ $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
1112
+ } else {
1113
+ $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')';
1114
+ $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)';
1115
+ $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
1116
+ }
1117
+ break;
1118
+ case 'restoreliteral':
1119
+ $regex = '<!--###literal(\d+)###-->';
1120
+ break;
1121
+ case 'include':
1122
+ $name = 'file';
1123
+ case 'taglib':
1124
+ case 'layout':
1125
+ case 'extend':
1126
+ if (empty($name)) {
1127
+ $name = 'name';
1128
+ }
1129
+ if ($single) {
1130
+ $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end;
1131
+ } else {
1132
+ $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end;
1133
+ }
1134
+ break;
1135
+ }
1136
+ }
1137
+ return '/' . $regex . '/is';
1138
+ }
1139
+ }
thinkphp/library/think/Url.php ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class Url
15
+ {
16
+ // 生成URL地址的root
17
+ protected static $root;
18
+ protected static $bindCheck;
19
+
20
+ /**
21
+ * URL生成 支持路由反射
22
+ * @param string $url 路由地址
23
+ * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2']
24
+ * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值
25
+ * @param boolean|string $domain 是否显示域名 或者直接传入域名
26
+ * @return string
27
+ */
28
+ public static function build($url = '', $vars = '', $suffix = true, $domain = false)
29
+ {
30
+ if (false === $domain && Route::rules('domain')) {
31
+ $domain = true;
32
+ }
33
+ // 解析URL
34
+ if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
35
+ // [name] 表示使用路由命名标识生成URL
36
+ $name = substr($url, 1, $pos - 1);
37
+ $url = 'name' . substr($url, $pos + 1);
38
+ }
39
+ if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
40
+ $info = parse_url($url);
41
+ $url = !empty($info['path']) ? $info['path'] : '';
42
+ if (isset($info['fragment'])) {
43
+ // 解析锚点
44
+ $anchor = $info['fragment'];
45
+ if (false !== strpos($anchor, '?')) {
46
+ // 解析参数
47
+ list($anchor, $info['query']) = explode('?', $anchor, 2);
48
+ }
49
+ if (false !== strpos($anchor, '@')) {
50
+ // 解析域名
51
+ list($anchor, $domain) = explode('@', $anchor, 2);
52
+ }
53
+ } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
54
+ // 解析域名
55
+ list($url, $domain) = explode('@', $url, 2);
56
+ }
57
+ }
58
+
59
+ // 解析参数
60
+ if (is_string($vars)) {
61
+ // aaa=1&bbb=2 转换成数组
62
+ parse_str($vars, $vars);
63
+ }
64
+
65
+ if ($url) {
66
+ $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''));
67
+ if (is_null($rule) && isset($info['query'])) {
68
+ $rule = Route::name($url);
69
+ // 解析地址里面参数 合并到vars
70
+ parse_str($info['query'], $params);
71
+ $vars = array_merge($params, $vars);
72
+ unset($info['query']);
73
+ }
74
+ }
75
+ if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) {
76
+ // 匹配路由命名标识
77
+ $url = $match[0];
78
+ // 替换可选分隔符
79
+ $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url);
80
+ if (!empty($match[1])) {
81
+ $domain = $match[1];
82
+ }
83
+ if (!is_null($match[2])) {
84
+ $suffix = $match[2];
85
+ }
86
+ } elseif (!empty($rule) && isset($name)) {
87
+ throw new \InvalidArgumentException('route name not exists:' . $name);
88
+ } else {
89
+ // 检查别名路由
90
+ $alias = Route::rules('alias');
91
+ $matchAlias = false;
92
+ if ($alias) {
93
+ // 别名路由解析
94
+ foreach ($alias as $key => $val) {
95
+ if (is_array($val)) {
96
+ $val = $val[0];
97
+ }
98
+ if (0 === strpos($url, $val)) {
99
+ $url = $key . substr($url, strlen($val));
100
+ $matchAlias = true;
101
+ break;
102
+ }
103
+ }
104
+ }
105
+ if (!$matchAlias) {
106
+ // 路由标识不存在 直接解析
107
+ $url = self::parseUrl($url, $domain);
108
+ }
109
+ if (isset($info['query'])) {
110
+ // 解析地址里面参数 合并到vars
111
+ parse_str($info['query'], $params);
112
+ $vars = array_merge($params, $vars);
113
+ }
114
+ }
115
+
116
+ // 检测URL绑定
117
+ if (!self::$bindCheck) {
118
+ $type = Route::getBind('type');
119
+ if ($type) {
120
+ $bind = Route::getBind($type);
121
+ if ($bind && 0 === strpos($url, $bind)) {
122
+ $url = substr($url, strlen($bind) + 1);
123
+ }
124
+ }
125
+ }
126
+ // 还原URL分隔符
127
+ $depr = Config::get('pathinfo_depr');
128
+ $url = str_replace('/', $depr, $url);
129
+
130
+ // URL后缀
131
+ $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix);
132
+ // 锚点
133
+ $anchor = !empty($anchor) ? '#' . $anchor : '';
134
+ // 参数组装
135
+ if (!empty($vars)) {
136
+ // 添加参数
137
+ if (Config::get('url_common_param')) {
138
+ $vars = http_build_query($vars);
139
+ $url .= $suffix . '?' . $vars . $anchor;
140
+ } else {
141
+ $paramType = Config::get('url_param_type');
142
+ foreach ($vars as $var => $val) {
143
+ if ('' !== trim($val)) {
144
+ if ($paramType) {
145
+ $url .= $depr . urlencode($val);
146
+ } else {
147
+ $url .= $depr . $var . $depr . urlencode($val);
148
+ }
149
+ }
150
+ }
151
+ $url .= $suffix . $anchor;
152
+ }
153
+ } else {
154
+ $url .= $suffix . $anchor;
155
+ }
156
+ // 检测域名
157
+ $domain = self::parseDomain($url, $domain);
158
+ // URL组装
159
+ $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/');
160
+
161
+ self::$bindCheck = false;
162
+ return $url;
163
+ }
164
+
165
+ // 直接解析URL地址
166
+ protected static function parseUrl($url, &$domain)
167
+ {
168
+ $request = Request::instance();
169
+ if (0 === strpos($url, '/')) {
170
+ // 直接作为路由地址解析
171
+ $url = substr($url, 1);
172
+ } elseif (false !== strpos($url, '\\')) {
173
+ // 解析到类
174
+ $url = ltrim(str_replace('\\', '/', $url), '/');
175
+ } elseif (0 === strpos($url, '@')) {
176
+ // 解析到控制器
177
+ $url = substr($url, 1);
178
+ } else {
179
+ // 解析到 模块/控制器/操作
180
+ $module = $request->module();
181
+ $domains = Route::rules('domain');
182
+ if (true === $domain && 2 == substr_count($url, '/')) {
183
+ $current = $request->host();
184
+ $match = [];
185
+ $pos = [];
186
+ foreach ($domains as $key => $item) {
187
+ if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) {
188
+ $pos[$key] = strlen($item['[bind]'][0]) + 1;
189
+ $match[] = $key;
190
+ $module = '';
191
+ }
192
+ }
193
+ if ($match) {
194
+ $domain = current($match);
195
+ foreach ($match as $item) {
196
+ if (0 === strpos($current, $item)) {
197
+ $domain = $item;
198
+ }
199
+ }
200
+ self::$bindCheck = true;
201
+ $url = substr($url, $pos[$domain]);
202
+ }
203
+ } elseif ($domain) {
204
+ if (isset($domains[$domain]['[bind]'][0])) {
205
+ $bindModule = $domains[$domain]['[bind]'][0];
206
+ if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) {
207
+ $module = '';
208
+ }
209
+ }
210
+ }
211
+ $module = $module ? $module . '/' : '';
212
+
213
+ $controller = $request->controller();
214
+ if ('' == $url) {
215
+ // 空字符串输出当前的 模块/控制器/操作
216
+ $action = $request->action();
217
+ } else {
218
+ $path = explode('/', $url);
219
+ $action = array_pop($path);
220
+ $controller = empty($path) ? $controller : array_pop($path);
221
+ $module = empty($path) ? $module : array_pop($path) . '/';
222
+ }
223
+ if (Config::get('url_convert')) {
224
+ $action = strtolower($action);
225
+ $controller = Loader::parseName($controller);
226
+ }
227
+ $url = $module . $controller . '/' . $action;
228
+ }
229
+ return $url;
230
+ }
231
+
232
+ // 检测域名
233
+ protected static function parseDomain(&$url, $domain)
234
+ {
235
+ if (!$domain) {
236
+ return '';
237
+ }
238
+ $request = Request::instance();
239
+ $rootDomain = Config::get('url_domain_root');
240
+ if (true === $domain) {
241
+ // 自动判断域名
242
+ $domain = Config::get('app_host') ?: $request->host();
243
+
244
+ $domains = Route::rules('domain');
245
+ if ($domains) {
246
+ $route_domain = array_keys($domains);
247
+ foreach ($route_domain as $domain_prefix) {
248
+ if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) {
249
+ foreach ($domains as $key => $rule) {
250
+ $rule = is_array($rule) ? $rule[0] : $rule;
251
+ if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) {
252
+ $url = ltrim($url, $rule);
253
+ $domain = $key;
254
+ // 生成对应子域名
255
+ if (!empty($rootDomain)) {
256
+ $domain .= $rootDomain;
257
+ }
258
+ break;
259
+ } elseif (false !== strpos($key, '*')) {
260
+ if (!empty($rootDomain)) {
261
+ $domain .= $rootDomain;
262
+ }
263
+ break;
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ } else {
271
+ if (empty($rootDomain)) {
272
+ $host = Config::get('app_host') ?: $request->host();
273
+ $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host;
274
+ }
275
+ if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) {
276
+ $domain .= '.' . $rootDomain;
277
+ }
278
+ }
279
+ if (false !== strpos($domain, '://')) {
280
+ $scheme = '';
281
+ } else {
282
+ $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://';
283
+ }
284
+ return $scheme . $domain;
285
+ }
286
+
287
+ // 解析URL后缀
288
+ protected static function parseSuffix($suffix)
289
+ {
290
+ if ($suffix) {
291
+ $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix;
292
+ if ($pos = strpos($suffix, '|')) {
293
+ $suffix = substr($suffix, 0, $pos);
294
+ }
295
+ }
296
+ return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix;
297
+ }
298
+
299
+ // 匹配路由地址
300
+ public static function getRuleUrl($rule, &$vars = [])
301
+ {
302
+ foreach ($rule as $item) {
303
+ list($url, $pattern, $domain, $suffix) = $item;
304
+ if (empty($pattern)) {
305
+ return [rtrim($url, '$'), $domain, $suffix];
306
+ }
307
+ $type = Config::get('url_common_param');
308
+ foreach ($pattern as $key => $val) {
309
+ if (isset($vars[$key])) {
310
+ $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url);
311
+ unset($vars[$key]);
312
+ $result = [$url, $domain, $suffix];
313
+ } elseif (2 == $val) {
314
+ $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
315
+ $result = [$url, $domain, $suffix];
316
+ } else {
317
+ break;
318
+ }
319
+ }
320
+ if (isset($result)) {
321
+ return $result;
322
+ }
323
+ }
324
+ return false;
325
+ }
326
+
327
+ // 指定当前生成URL地址的root
328
+ public static function root($root)
329
+ {
330
+ self::$root = $root;
331
+ Request::instance()->root($root);
332
+ }
333
+ }
thinkphp/library/think/Validate.php ADDED
@@ -0,0 +1,1371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ use think\exception\ClassNotFoundException;
15
+
16
+ class Validate
17
+ {
18
+ // 实例
19
+ protected static $instance;
20
+
21
+ // 自定义的验证类型
22
+ protected static $type = [];
23
+
24
+ // 验证类型别名
25
+ protected $alias = [
26
+ '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq',
27
+ ];
28
+
29
+ // 当前验证的规则
30
+ protected $rule = [];
31
+
32
+ // 验证提示信息
33
+ protected $message = [];
34
+ // 验证字段描述
35
+ protected $field = [];
36
+
37
+ // 验证规则默认提示信息
38
+ protected static $typeMsg = [
39
+ 'require' => ':attribute require',
40
+ 'number' => ':attribute must be numeric',
41
+ 'integer' => ':attribute must be integer',
42
+ 'float' => ':attribute must be float',
43
+ 'boolean' => ':attribute must be bool',
44
+ 'email' => ':attribute not a valid email address',
45
+ 'array' => ':attribute must be a array',
46
+ 'accepted' => ':attribute must be yes,on or 1',
47
+ 'date' => ':attribute not a valid datetime',
48
+ 'file' => ':attribute not a valid file',
49
+ 'image' => ':attribute not a valid image',
50
+ 'alpha' => ':attribute must be alpha',
51
+ 'alphaNum' => ':attribute must be alpha-numeric',
52
+ 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore',
53
+ 'activeUrl' => ':attribute not a valid domain or ip',
54
+ 'chs' => ':attribute must be chinese',
55
+ 'chsAlpha' => ':attribute must be chinese or alpha',
56
+ 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric',
57
+ 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash',
58
+ 'url' => ':attribute not a valid url',
59
+ 'ip' => ':attribute not a valid ip',
60
+ 'dateFormat' => ':attribute must be dateFormat of :rule',
61
+ 'in' => ':attribute must be in :rule',
62
+ 'notIn' => ':attribute be notin :rule',
63
+ 'between' => ':attribute must between :1 - :2',
64
+ 'notBetween' => ':attribute not between :1 - :2',
65
+ 'length' => 'size of :attribute must be :rule',
66
+ 'max' => 'max size of :attribute must be :rule',
67
+ 'min' => 'min size of :attribute must be :rule',
68
+ 'after' => ':attribute cannot be less than :rule',
69
+ 'before' => ':attribute cannot exceed :rule',
70
+ 'afterWith' => ':attribute cannot be less than :rule',
71
+ 'beforeWith' => ':attribute cannot exceed :rule',
72
+ 'expire' => ':attribute not within :rule',
73
+ 'allowIp' => 'access IP is not allowed',
74
+ 'denyIp' => 'access IP denied',
75
+ 'confirm' => ':attribute out of accord with :2',
76
+ 'different' => ':attribute cannot be same with :2',
77
+ 'egt' => ':attribute must greater than or equal :rule',
78
+ 'gt' => ':attribute must greater than :rule',
79
+ 'elt' => ':attribute must less than or equal :rule',
80
+ 'lt' => ':attribute must less than :rule',
81
+ 'eq' => ':attribute must equal :rule',
82
+ 'unique' => ':attribute has exists',
83
+ 'regex' => ':attribute not conform to the rules',
84
+ 'method' => 'invalid Request method',
85
+ 'token' => 'invalid token',
86
+ 'fileSize' => 'filesize not match',
87
+ 'fileExt' => 'extensions to upload is not allowed',
88
+ 'fileMime' => 'mimetype to upload is not allowed',
89
+ ];
90
+
91
+ // 当前验证场景
92
+ protected $currentScene = null;
93
+
94
+ // 正则表达式 regex = ['zip'=>'\d{6}',...]
95
+ protected $regex = [];
96
+
97
+ // 验证场景 scene = ['edit'=>'name1,name2,...']
98
+ protected $scene = [];
99
+
100
+ // 验证失败错误信息
101
+ protected $error = [];
102
+
103
+ // 批量验证
104
+ protected $batch = false;
105
+
106
+ /**
107
+ * 构造函数
108
+ * @access public
109
+ * @param array $rules 验证规则
110
+ * @param array $message 验证提示信息
111
+ * @param array $field 验证字段描述信息
112
+ */
113
+ public function __construct(array $rules = [], $message = [], $field = [])
114
+ {
115
+ $this->rule = array_merge($this->rule, $rules);
116
+ $this->message = array_merge($this->message, $message);
117
+ $this->field = array_merge($this->field, $field);
118
+ }
119
+
120
+ /**
121
+ * 实例化验证
122
+ * @access public
123
+ * @param array $rules 验证规则
124
+ * @param array $message 验证提示信息
125
+ * @param array $field 验证字段描述信息
126
+ * @return Validate
127
+ */
128
+ public static function make($rules = [], $message = [], $field = [])
129
+ {
130
+ if (is_null(self::$instance)) {
131
+ self::$instance = new self($rules, $message, $field);
132
+ }
133
+ return self::$instance;
134
+ }
135
+
136
+ /**
137
+ * 添加字段验证规则
138
+ * @access protected
139
+ * @param string|array $name 字段名称或者规则数组
140
+ * @param mixed $rule 验证规则
141
+ * @return Validate
142
+ */
143
+ public function rule($name, $rule = '')
144
+ {
145
+ if (is_array($name)) {
146
+ $this->rule = array_merge($this->rule, $name);
147
+ } else {
148
+ $this->rule[$name] = $rule;
149
+ }
150
+ return $this;
151
+ }
152
+
153
+ /**
154
+ * 注册验证(类型)规则
155
+ * @access public
156
+ * @param string $type 验证规则类型
157
+ * @param mixed $callback callback方法(或闭包)
158
+ * @return void
159
+ */
160
+ public static function extend($type, $callback = null)
161
+ {
162
+ if (is_array($type)) {
163
+ self::$type = array_merge(self::$type, $type);
164
+ } else {
165
+ self::$type[$type] = $callback;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 设置验证规则的默认提示信息
171
+ * @access protected
172
+ * @param string|array $type 验证规则类型名称或者数组
173
+ * @param string $msg 验证提示信息
174
+ * @return void
175
+ */
176
+ public static function setTypeMsg($type, $msg = null)
177
+ {
178
+ if (is_array($type)) {
179
+ self::$typeMsg = array_merge(self::$typeMsg, $type);
180
+ } else {
181
+ self::$typeMsg[$type] = $msg;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * 设置提示信息
187
+ * @access public
188
+ * @param string|array $name 字段名称
189
+ * @param string $message 提示信息
190
+ * @return Validate
191
+ */
192
+ public function message($name, $message = '')
193
+ {
194
+ if (is_array($name)) {
195
+ $this->message = array_merge($this->message, $name);
196
+ } else {
197
+ $this->message[$name] = $message;
198
+ }
199
+ return $this;
200
+ }
201
+
202
+ /**
203
+ * 设置验证场景
204
+ * @access public
205
+ * @param string|array $name 场景名或者场景设置数组
206
+ * @param mixed $fields 要验证的字段
207
+ * @return Validate
208
+ */
209
+ public function scene($name, $fields = null)
210
+ {
211
+ if (is_array($name)) {
212
+ $this->scene = array_merge($this->scene, $name);
213
+ }if (is_null($fields)) {
214
+ // 设置当前场景
215
+ $this->currentScene = $name;
216
+ } else {
217
+ // 设置验证场景
218
+ $this->scene[$name] = $fields;
219
+ }
220
+ return $this;
221
+ }
222
+
223
+ /**
224
+ * 判断是否存在某个验证场景
225
+ * @access public
226
+ * @param string $name 场景名
227
+ * @return bool
228
+ */
229
+ public function hasScene($name)
230
+ {
231
+ return isset($this->scene[$name]);
232
+ }
233
+
234
+ /**
235
+ * 设置批量验证
236
+ * @access public
237
+ * @param bool $batch 是否批量验证
238
+ * @return Validate
239
+ */
240
+ public function batch($batch = true)
241
+ {
242
+ $this->batch = $batch;
243
+ return $this;
244
+ }
245
+
246
+ /**
247
+ * 数据自动验证
248
+ * @access public
249
+ * @param array $data 数据
250
+ * @param mixed $rules 验证规则
251
+ * @param string $scene 验证场景
252
+ * @return bool
253
+ */
254
+ public function check($data, $rules = [], $scene = '')
255
+ {
256
+ $this->error = [];
257
+
258
+ if (empty($rules)) {
259
+ // 读取验证规则
260
+ $rules = $this->rule;
261
+ }
262
+
263
+ // 分析验证规则
264
+ $scene = $this->getScene($scene);
265
+ if (is_array($scene)) {
266
+ // 处理场景验证字段
267
+ $change = [];
268
+ $array = [];
269
+ foreach ($scene as $k => $val) {
270
+ if (is_numeric($k)) {
271
+ $array[] = $val;
272
+ } else {
273
+ $array[] = $k;
274
+ $change[$k] = $val;
275
+ }
276
+ }
277
+ }
278
+
279
+ foreach ($rules as $key => $item) {
280
+ // field => rule1|rule2... field=>['rule1','rule2',...]
281
+ if (is_numeric($key)) {
282
+ // [field,rule1|rule2,msg1|msg2]
283
+ $key = $item[0];
284
+ $rule = $item[1];
285
+ if (isset($item[2])) {
286
+ $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2];
287
+ } else {
288
+ $msg = [];
289
+ }
290
+ } else {
291
+ $rule = $item;
292
+ $msg = [];
293
+ }
294
+ if (strpos($key, '|')) {
295
+ // 字段|描述 用于指定属性名称
296
+ list($key, $title) = explode('|', $key);
297
+ } else {
298
+ $title = isset($this->field[$key]) ? $this->field[$key] : $key;
299
+ }
300
+
301
+ // 场景检测
302
+ if (!empty($scene)) {
303
+ if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) {
304
+ continue;
305
+ } elseif (is_array($scene)) {
306
+ if (!in_array($key, $array)) {
307
+ continue;
308
+ } elseif (isset($change[$key])) {
309
+ // 重载某个验证规则
310
+ $rule = $change[$key];
311
+ }
312
+ }
313
+ }
314
+
315
+ // 获取数据 支持二维数组
316
+ $value = $this->getDataValue($data, $key);
317
+
318
+ // 字段验证
319
+ if ($rule instanceof \Closure) {
320
+ // 匿名函数验证 支持传入当前字段和所有字段两个数据
321
+ $result = call_user_func_array($rule, [$value, $data]);
322
+ } else {
323
+ $result = $this->checkItem($key, $value, $rule, $data, $title, $msg);
324
+ }
325
+
326
+ if (true !== $result) {
327
+ // 没有返回true 则表示验证失败
328
+ if (!empty($this->batch)) {
329
+ // 批量验证
330
+ if (is_array($result)) {
331
+ $this->error = array_merge($this->error, $result);
332
+ } else {
333
+ $this->error[$key] = $result;
334
+ }
335
+ } else {
336
+ $this->error = $result;
337
+ return false;
338
+ }
339
+ }
340
+ }
341
+ return !empty($this->error) ? false : true;
342
+ }
343
+
344
+ /**
345
+ * 根据验证规则验证数据
346
+ * @access protected
347
+ * @param mixed $value 字段值
348
+ * @param mixed $rules 验证规则
349
+ * @return bool
350
+ */
351
+ protected function checkRule($value, $rules)
352
+ {
353
+ if ($rules instanceof \Closure) {
354
+ return call_user_func_array($rules, [$value]);
355
+ } elseif (is_string($rules)) {
356
+ $rules = explode('|', $rules);
357
+ }
358
+
359
+ foreach ($rules as $key => $rule) {
360
+ if ($rule instanceof \Closure) {
361
+ $result = call_user_func_array($rule, [$value]);
362
+ } else {
363
+ // 判断验证类型
364
+ list($type, $rule) = $this->getValidateType($key, $rule);
365
+
366
+ $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type];
367
+
368
+ $result = call_user_func_array($callback, [$value, $rule]);
369
+ }
370
+
371
+ if (true !== $result) {
372
+ return $result;
373
+ }
374
+ }
375
+
376
+ return true;
377
+ }
378
+
379
+ /**
380
+ * 验证单个字段规则
381
+ * @access protected
382
+ * @param string $field 字段名
383
+ * @param mixed $value 字段值
384
+ * @param mixed $rules 验证规则
385
+ * @param array $data 数据
386
+ * @param string $title 字段描述
387
+ * @param array $msg 提示信息
388
+ * @return mixed
389
+ */
390
+ protected function checkItem($field, $value, $rules, $data, $title = '', $msg = [])
391
+ {
392
+ // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
393
+ if (is_string($rules)) {
394
+ $rules = explode('|', $rules);
395
+ }
396
+ $i = 0;
397
+ foreach ($rules as $key => $rule) {
398
+ if ($rule instanceof \Closure) {
399
+ $result = call_user_func_array($rule, [$value, $data]);
400
+ $info = is_numeric($key) ? '' : $key;
401
+ } else {
402
+ // 判断验证类型
403
+ list($type, $rule, $info) = $this->getValidateType($key, $rule);
404
+
405
+ // 如果不是require 有数据才会行验证
406
+ if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) {
407
+ // 验证类型
408
+ $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type];
409
+ // 验证数据
410
+ $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]);
411
+ } else {
412
+ $result = true;
413
+ }
414
+ }
415
+
416
+ if (false === $result) {
417
+ // 验证失败 返回错误信息
418
+ if (isset($msg[$i])) {
419
+ $message = $msg[$i];
420
+ if (is_string($message) && strpos($message, '{%') === 0) {
421
+ $message = Lang::get(substr($message, 2, -1));
422
+ }
423
+ } else {
424
+ $message = $this->getRuleMsg($field, $title, $info, $rule);
425
+ }
426
+ return $message;
427
+ } elseif (true !== $result) {
428
+ // 返回自定义错误信息
429
+ if (is_string($result) && false !== strpos($result, ':')) {
430
+ $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result);
431
+ }
432
+ return $result;
433
+ }
434
+ $i++;
435
+ }
436
+ return $result;
437
+ }
438
+
439
+ /**
440
+ * 获取当前验证类型及规则
441
+ * @access public
442
+ * @param mixed $key
443
+ * @param mixed $rule
444
+ * @return array
445
+ */
446
+ protected function getValidateType($key, $rule)
447
+ {
448
+ // 判断验证类型
449
+ if (!is_numeric($key)) {
450
+ return [$key, $rule, $key];
451
+ }
452
+
453
+ if (strpos($rule, ':')) {
454
+ list($type, $rule) = explode(':', $rule, 2);
455
+ if (isset($this->alias[$type])) {
456
+ // 判断别名
457
+ $type = $this->alias[$type];
458
+ }
459
+ $info = $type;
460
+ } elseif (method_exists($this, $rule)) {
461
+ $type = $rule;
462
+ $info = $rule;
463
+ $rule = '';
464
+ } else {
465
+ $type = 'is';
466
+ $info = $rule;
467
+ }
468
+
469
+ return [$type, $rule, $info];
470
+ }
471
+
472
+ /**
473
+ * 验证是否和某个字段的值一致
474
+ * @access protected
475
+ * @param mixed $value 字段值
476
+ * @param mixed $rule 验证规则
477
+ * @param array $data 数据
478
+ * @param string $field 字段名
479
+ * @return bool
480
+ */
481
+ protected function confirm($value, $rule, $data, $field = '')
482
+ {
483
+ if ('' == $rule) {
484
+ if (strpos($field, '_confirm')) {
485
+ $rule = strstr($field, '_confirm', true);
486
+ } else {
487
+ $rule = $field . '_confirm';
488
+ }
489
+ }
490
+ return $this->getDataValue($data, $rule) === $value;
491
+ }
492
+
493
+ /**
494
+ * 验证是否和某个字段的值是否不同
495
+ * @access protected
496
+ * @param mixed $value 字段值
497
+ * @param mixed $rule 验证规则
498
+ * @param array $data 数据
499
+ * @return bool
500
+ */
501
+ protected function different($value, $rule, $data)
502
+ {
503
+ return $this->getDataValue($data, $rule) != $value;
504
+ }
505
+
506
+ /**
507
+ * 验证是否大于等于某个值
508
+ * @access protected
509
+ * @param mixed $value 字段值
510
+ * @param mixed $rule 验证规则
511
+ * @param array $data 数据
512
+ * @return bool
513
+ */
514
+ protected function egt($value, $rule, $data)
515
+ {
516
+ $val = $this->getDataValue($data, $rule);
517
+ return !is_null($val) && $value >= $val;
518
+ }
519
+
520
+ /**
521
+ * 验证是否大于某个值
522
+ * @access protected
523
+ * @param mixed $value 字段值
524
+ * @param mixed $rule 验证规则
525
+ * @param array $data 数据
526
+ * @return bool
527
+ */
528
+ protected function gt($value, $rule, $data)
529
+ {
530
+ $val = $this->getDataValue($data, $rule);
531
+ return !is_null($val) && $value > $val;
532
+ }
533
+
534
+ /**
535
+ * 验证是否小于等于某个值
536
+ * @access protected
537
+ * @param mixed $value 字段值
538
+ * @param mixed $rule 验证规则
539
+ * @param array $data 数据
540
+ * @return bool
541
+ */
542
+ protected function elt($value, $rule, $data)
543
+ {
544
+ $val = $this->getDataValue($data, $rule);
545
+ return !is_null($val) && $value <= $val;
546
+ }
547
+
548
+ /**
549
+ * 验证是否小于某个值
550
+ * @access protected
551
+ * @param mixed $value 字段值
552
+ * @param mixed $rule 验证规则
553
+ * @param array $data 数据
554
+ * @return bool
555
+ */
556
+ protected function lt($value, $rule, $data)
557
+ {
558
+ $val = $this->getDataValue($data, $rule);
559
+ return !is_null($val) && $value < $val;
560
+ }
561
+
562
+ /**
563
+ * 验证是否等于某个值
564
+ * @access protected
565
+ * @param mixed $value 字段值
566
+ * @param mixed $rule 验证规则
567
+ * @return bool
568
+ */
569
+ protected function eq($value, $rule)
570
+ {
571
+ return $value == $rule;
572
+ }
573
+
574
+ /**
575
+ * 验证字段值是否为有效格式
576
+ * @access protected
577
+ * @param mixed $value 字段值
578
+ * @param string $rule 验证规则
579
+ * @param array $data 验证数据
580
+ * @return bool
581
+ */
582
+ protected function is($value, $rule, $data = [])
583
+ {
584
+ switch ($rule) {
585
+ case 'require':
586
+ // 必须
587
+ $result = !empty($value) || '0' == $value;
588
+ break;
589
+ case 'accepted':
590
+ // 接受
591
+ $result = in_array($value, ['1', 'on', 'yes']);
592
+ break;
593
+ case 'date':
594
+ // 是否是一个有效日期
595
+ $result = false !== strtotime($value);
596
+ break;
597
+ case 'alpha':
598
+ // 只允许字母
599
+ $result = $this->regex($value, '/^[A-Za-z]+$/');
600
+ break;
601
+ case 'alphaNum':
602
+ // 只允许字母和数字
603
+ $result = $this->regex($value, '/^[A-Za-z0-9]+$/');
604
+ break;
605
+ case 'alphaDash':
606
+ // 只允许字母、数字和下划线 破折号
607
+ $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/');
608
+ break;
609
+ case 'chs':
610
+ // 只允许汉字
611
+ $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u');
612
+ break;
613
+ case 'chsAlpha':
614
+ // 只允许汉字、字母
615
+ $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u');
616
+ break;
617
+ case 'chsAlphaNum':
618
+ // 只允许汉字、字母和数字
619
+ $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u');
620
+ break;
621
+ case 'chsDash':
622
+ // 只允许汉字、字母、数字和下划线_及破折号-
623
+ $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u');
624
+ break;
625
+ case 'activeUrl':
626
+ // 是否为有效的网址
627
+ $result = checkdnsrr($value);
628
+ break;
629
+ case 'ip':
630
+ // 是否为IP地址
631
+ $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]);
632
+ break;
633
+ case 'url':
634
+ // 是否为一个URL地址
635
+ $result = $this->filter($value, FILTER_VALIDATE_URL);
636
+ break;
637
+ case 'float':
638
+ // 是否为float
639
+ $result = $this->filter($value, FILTER_VALIDATE_FLOAT);
640
+ break;
641
+ case 'number':
642
+ $result = is_numeric($value);
643
+ break;
644
+ case 'integer':
645
+ // 是否为整型
646
+ $result = $this->filter($value, FILTER_VALIDATE_INT);
647
+ break;
648
+ case 'email':
649
+ // 是否为邮箱地址
650
+ $result = $this->filter($value, FILTER_VALIDATE_EMAIL);
651
+ break;
652
+ case 'boolean':
653
+ // 是否为布尔值
654
+ $result = in_array($value, [true, false, 0, 1, '0', '1'], true);
655
+ break;
656
+ case 'array':
657
+ // 是否为数组
658
+ $result = is_array($value);
659
+ break;
660
+ case 'file':
661
+ $result = $value instanceof File;
662
+ break;
663
+ case 'image':
664
+ $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]);
665
+ break;
666
+ case 'token':
667
+ $result = $this->token($value, '__token__', $data);
668
+ break;
669
+ default:
670
+ if (isset(self::$type[$rule])) {
671
+ // 注册的验证规则
672
+ $result = call_user_func_array(self::$type[$rule], [$value]);
673
+ } else {
674
+ // 正则验证
675
+ $result = $this->regex($value, $rule);
676
+ }
677
+ }
678
+ return $result;
679
+ }
680
+
681
+ // 判断图像类型
682
+ protected function getImageType($image)
683
+ {
684
+ if (function_exists('exif_imagetype')) {
685
+ return exif_imagetype($image);
686
+ } else {
687
+ try {
688
+ $info = getimagesize($image);
689
+ return $info ? $info[2] : false;
690
+ } catch (\Exception $e) {
691
+ return false;
692
+ }
693
+ }
694
+ }
695
+
696
+ /**
697
+ * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
698
+ * @access protected
699
+ * @param mixed $value 字段值
700
+ * @param mixed $rule 验证规则
701
+ * @return bool
702
+ */
703
+ protected function activeUrl($value, $rule)
704
+ {
705
+ if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
706
+ $rule = 'MX';
707
+ }
708
+ return checkdnsrr($value, $rule);
709
+ }
710
+
711
+ /**
712
+ * 验证是否有效IP
713
+ * @access protected
714
+ * @param mixed $value 字段值
715
+ * @param mixed $rule 验证规则 ipv4 ipv6
716
+ * @return bool
717
+ */
718
+ protected function ip($value, $rule)
719
+ {
720
+ if (!in_array($rule, ['ipv4', 'ipv6'])) {
721
+ $rule = 'ipv4';
722
+ }
723
+ return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
724
+ }
725
+
726
+ /**
727
+ * 验证上传文件后缀
728
+ * @access protected
729
+ * @param mixed $file 上传文件
730
+ * @param mixed $rule 验证规则
731
+ * @return bool
732
+ */
733
+ protected function fileExt($file, $rule)
734
+ {
735
+ if (is_array($file)) {
736
+ foreach ($file as $item) {
737
+ if (!($item instanceof File) || !$item->checkExt($rule)) {
738
+ return false;
739
+ }
740
+ }
741
+ return true;
742
+ } elseif ($file instanceof File) {
743
+ return $file->checkExt($rule);
744
+ } else {
745
+ return false;
746
+ }
747
+ }
748
+
749
+ /**
750
+ * 验证上传文件类型
751
+ * @access protected
752
+ * @param mixed $file 上传文件
753
+ * @param mixed $rule 验证规则
754
+ * @return bool
755
+ */
756
+ protected function fileMime($file, $rule)
757
+ {
758
+ if (is_array($file)) {
759
+ foreach ($file as $item) {
760
+ if (!($item instanceof File) || !$item->checkMime($rule)) {
761
+ return false;
762
+ }
763
+ }
764
+ return true;
765
+ } elseif ($file instanceof File) {
766
+ return $file->checkMime($rule);
767
+ } else {
768
+ return false;
769
+ }
770
+ }
771
+
772
+ /**
773
+ * 验证上传文件大小
774
+ * @access protected
775
+ * @param mixed $file 上传文件
776
+ * @param mixed $rule 验证规则
777
+ * @return bool
778
+ */
779
+ protected function fileSize($file, $rule)
780
+ {
781
+ if (is_array($file)) {
782
+ foreach ($file as $item) {
783
+ if (!($item instanceof File) || !$item->checkSize($rule)) {
784
+ return false;
785
+ }
786
+ }
787
+ return true;
788
+ } elseif ($file instanceof File) {
789
+ return $file->checkSize($rule);
790
+ } else {
791
+ return false;
792
+ }
793
+ }
794
+
795
+ /**
796
+ * 验证图片的宽高及类型
797
+ * @access protected
798
+ * @param mixed $file 上传文件
799
+ * @param mixed $rule 验证规则
800
+ * @return bool
801
+ */
802
+ protected function image($file, $rule)
803
+ {
804
+ if (!($file instanceof File)) {
805
+ return false;
806
+ }
807
+ if ($rule) {
808
+ $rule = explode(',', $rule);
809
+ list($width, $height, $type) = getimagesize($file->getRealPath());
810
+ if (isset($rule[2])) {
811
+ $imageType = strtolower($rule[2]);
812
+ if ('jpeg' == $imageType) {
813
+ $imageType = 'jpg';
814
+ }
815
+ if (image_type_to_extension($type, false) != $imageType) {
816
+ return false;
817
+ }
818
+ }
819
+
820
+ list($w, $h) = $rule;
821
+ return $w == $width && $h == $height;
822
+ } else {
823
+ return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
824
+ }
825
+ }
826
+
827
+ /**
828
+ * 验证请求类型
829
+ * @access protected
830
+ * @param mixed $value 字段值
831
+ * @param mixed $rule 验证规则
832
+ * @return bool
833
+ */
834
+ protected function method($value, $rule)
835
+ {
836
+ $method = Request::instance()->method();
837
+ return strtoupper($rule) == $method;
838
+ }
839
+
840
+ /**
841
+ * 验证时间和日期是否符合指定格式
842
+ * @access protected
843
+ * @param mixed $value 字段值
844
+ * @param mixed $rule 验证规则
845
+ * @return bool
846
+ */
847
+ protected function dateFormat($value, $rule)
848
+ {
849
+ $info = date_parse_from_format($rule, $value);
850
+ return 0 == $info['warning_count'] && 0 == $info['error_count'];
851
+ }
852
+
853
+ /**
854
+ * 验证是否唯一
855
+ * @access protected
856
+ * @param mixed $value 字段值
857
+ * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名
858
+ * @param array $data 数据
859
+ * @param string $field 验证字段名
860
+ * @return bool
861
+ */
862
+ protected function unique($value, $rule, $data, $field)
863
+ {
864
+ if (is_string($rule)) {
865
+ $rule = explode(',', $rule);
866
+ }
867
+ if (false !== strpos($rule[0], '\\')) {
868
+ // 指定模型类
869
+ $db = new $rule[0];
870
+ } else {
871
+ try {
872
+ $db = Loader::model($rule[0]);
873
+ } catch (ClassNotFoundException $e) {
874
+ $db = Db::name($rule[0]);
875
+ }
876
+ }
877
+ $key = isset($rule[1]) ? $rule[1] : $field;
878
+
879
+ if (strpos($key, '^')) {
880
+ // 支持多个字段验证
881
+ $fields = explode('^', $key);
882
+ foreach ($fields as $key) {
883
+ if (isset($data[$key])) {
884
+ $map[$key] = $data[$key];
885
+ }
886
+ }
887
+ } elseif (strpos($key, '=')) {
888
+ parse_str($key, $map);
889
+ } elseif (isset($data[$field])) {
890
+ $map[$key] = $data[$field];
891
+ } else {
892
+ $map = [];
893
+ }
894
+
895
+ $pk = isset($rule[3]) ? $rule[3] : $db->getPk();
896
+ if (is_string($pk)) {
897
+ if (isset($rule[2])) {
898
+ $map[$pk] = ['neq', $rule[2]];
899
+ } elseif (isset($data[$pk])) {
900
+ $map[$pk] = ['neq', $data[$pk]];
901
+ }
902
+ }
903
+ if ($db->where($map)->field($pk)->find()) {
904
+ return false;
905
+ }
906
+ return true;
907
+ }
908
+
909
+ /**
910
+ * 使用行为类验证
911
+ * @access protected
912
+ * @param mixed $value 字段值
913
+ * @param mixed $rule 验证规则
914
+ * @param array $data 数据
915
+ * @return mixed
916
+ */
917
+ protected function behavior($value, $rule, $data)
918
+ {
919
+ return Hook::exec($rule, '', $data);
920
+ }
921
+
922
+ /**
923
+ * 使用filter_var方式验证
924
+ * @access protected
925
+ * @param mixed $value 字段值
926
+ * @param mixed $rule 验证规则
927
+ * @return bool
928
+ */
929
+ protected function filter($value, $rule)
930
+ {
931
+ if (is_string($rule) && strpos($rule, ',')) {
932
+ list($rule, $param) = explode(',', $rule);
933
+ } elseif (is_array($rule)) {
934
+ $param = isset($rule[1]) ? $rule[1] : null;
935
+ $rule = $rule[0];
936
+ } else {
937
+ $param = null;
938
+ }
939
+ return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
940
+ }
941
+
942
+ /**
943
+ * 验证某个字段等于某个值的时候必须
944
+ * @access protected
945
+ * @param mixed $value 字段值
946
+ * @param mixed $rule 验证规则
947
+ * @param array $data 数据
948
+ * @return bool
949
+ */
950
+ protected function requireIf($value, $rule, $data)
951
+ {
952
+ list($field, $val) = explode(',', $rule);
953
+ if ($this->getDataValue($data, $field) == $val) {
954
+ return !empty($value) || '0' == $value;
955
+ } else {
956
+ return true;
957
+ }
958
+ }
959
+
960
+ /**
961
+ * 通过回调方法验证某个字段是否必须
962
+ * @access protected
963
+ * @param mixed $value 字段值
964
+ * @param mixed $rule 验证规则
965
+ * @param array $data 数据
966
+ * @return bool
967
+ */
968
+ protected function requireCallback($value, $rule, $data)
969
+ {
970
+ $result = call_user_func_array($rule, [$value, $data]);
971
+ if ($result) {
972
+ return !empty($value) || '0' == $value;
973
+ } else {
974
+ return true;
975
+ }
976
+ }
977
+
978
+ /**
979
+ * 验证某个字段有值的情况下必须
980
+ * @access protected
981
+ * @param mixed $value 字段值
982
+ * @param mixed $rule 验证规则
983
+ * @param array $data 数据
984
+ * @return bool
985
+ */
986
+ protected function requireWith($value, $rule, $data)
987
+ {
988
+ $val = $this->getDataValue($data, $rule);
989
+ if (!empty($val)) {
990
+ return !empty($value) || '0' == $value;
991
+ } else {
992
+ return true;
993
+ }
994
+ }
995
+
996
+ /**
997
+ * 验证是否在范围内
998
+ * @access protected
999
+ * @param mixed $value 字段值
1000
+ * @param mixed $rule 验证规则
1001
+ * @return bool
1002
+ */
1003
+ protected function in($value, $rule)
1004
+ {
1005
+ return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1006
+ }
1007
+
1008
+ /**
1009
+ * 验证是否不在某个范围
1010
+ * @access protected
1011
+ * @param mixed $value 字段值
1012
+ * @param mixed $rule 验证规则
1013
+ * @return bool
1014
+ */
1015
+ protected function notIn($value, $rule)
1016
+ {
1017
+ return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1018
+ }
1019
+
1020
+ /**
1021
+ * between验证数据
1022
+ * @access protected
1023
+ * @param mixed $value 字段值
1024
+ * @param mixed $rule 验证规则
1025
+ * @return bool
1026
+ */
1027
+ protected function between($value, $rule)
1028
+ {
1029
+ if (is_string($rule)) {
1030
+ $rule = explode(',', $rule);
1031
+ }
1032
+ list($min, $max) = $rule;
1033
+ return $value >= $min && $value <= $max;
1034
+ }
1035
+
1036
+ /**
1037
+ * 使用notbetween验证数据
1038
+ * @access protected
1039
+ * @param mixed $value 字段值
1040
+ * @param mixed $rule 验证规则
1041
+ * @return bool
1042
+ */
1043
+ protected function notBetween($value, $rule)
1044
+ {
1045
+ if (is_string($rule)) {
1046
+ $rule = explode(',', $rule);
1047
+ }
1048
+ list($min, $max) = $rule;
1049
+ return $value < $min || $value > $max;
1050
+ }
1051
+
1052
+ /**
1053
+ * 验证数据长度
1054
+ * @access protected
1055
+ * @param mixed $value 字段值
1056
+ * @param mixed $rule 验证规则
1057
+ * @return bool
1058
+ */
1059
+ protected function length($value, $rule)
1060
+ {
1061
+ if (is_array($value)) {
1062
+ $length = count($value);
1063
+ } elseif ($value instanceof File) {
1064
+ $length = $value->getSize();
1065
+ } else {
1066
+ $length = mb_strlen((string) $value);
1067
+ }
1068
+
1069
+ if (strpos($rule, ',')) {
1070
+ // 长度区间
1071
+ list($min, $max) = explode(',', $rule);
1072
+ return $length >= $min && $length <= $max;
1073
+ } else {
1074
+ // 指定长度
1075
+ return $length == $rule;
1076
+ }
1077
+ }
1078
+
1079
+ /**
1080
+ * 验证数据最大长度
1081
+ * @access protected
1082
+ * @param mixed $value 字段值
1083
+ * @param mixed $rule 验证规则
1084
+ * @return bool
1085
+ */
1086
+ protected function max($value, $rule)
1087
+ {
1088
+ if (is_array($value)) {
1089
+ $length = count($value);
1090
+ } elseif ($value instanceof File) {
1091
+ $length = $value->getSize();
1092
+ } else {
1093
+ $length = mb_strlen((string) $value);
1094
+ }
1095
+ return $length <= $rule;
1096
+ }
1097
+
1098
+ /**
1099
+ * 验证数据最小长度
1100
+ * @access protected
1101
+ * @param mixed $value 字段值
1102
+ * @param mixed $rule 验证规则
1103
+ * @return bool
1104
+ */
1105
+ protected function min($value, $rule)
1106
+ {
1107
+ if (is_array($value)) {
1108
+ $length = count($value);
1109
+ } elseif ($value instanceof File) {
1110
+ $length = $value->getSize();
1111
+ } else {
1112
+ $length = mb_strlen((string) $value);
1113
+ }
1114
+ return $length >= $rule;
1115
+ }
1116
+
1117
+ /**
1118
+ * 验证日期
1119
+ * @access protected
1120
+ * @param mixed $value 字段值
1121
+ * @param mixed $rule 验证规则
1122
+ * @param array $data 数据
1123
+ * @return bool
1124
+ */
1125
+ protected function after($value, $rule, $data)
1126
+ {
1127
+ return strtotime($value) >= strtotime($rule);
1128
+ }
1129
+
1130
+ /**
1131
+ * 验证日期
1132
+ * @access protected
1133
+ * @param mixed $value 字段值
1134
+ * @param mixed $rule 验证规则
1135
+ * @param array $data 数据
1136
+ * @return bool
1137
+ */
1138
+ protected function before($value, $rule, $data)
1139
+ {
1140
+ return strtotime($value) <= strtotime($rule);
1141
+ }
1142
+
1143
+ /**
1144
+ * 验证日期字段
1145
+ * @access protected
1146
+ * @param mixed $value 字段值
1147
+ * @param mixed $rule 验证规则
1148
+ * @param array $data 数据
1149
+ * @return bool
1150
+ */
1151
+ protected function afterWith($value, $rule, $data)
1152
+ {
1153
+ $rule = $this->getDataValue($data, $rule);
1154
+ return !is_null($rule) && strtotime($value) >= strtotime($rule);
1155
+ }
1156
+
1157
+ /**
1158
+ * 验证日期字段
1159
+ * @access protected
1160
+ * @param mixed $value 字段值
1161
+ * @param mixed $rule 验证规则
1162
+ * @param array $data 数据
1163
+ * @return bool
1164
+ */
1165
+ protected function beforeWith($value, $rule, $data)
1166
+ {
1167
+ $rule = $this->getDataValue($data, $rule);
1168
+ return !is_null($rule) && strtotime($value) <= strtotime($rule);
1169
+ }
1170
+
1171
+ /**
1172
+ * 验证有效期
1173
+ * @access protected
1174
+ * @param mixed $value 字段值
1175
+ * @param mixed $rule 验证规则
1176
+ * @return bool
1177
+ */
1178
+ protected function expire($value, $rule)
1179
+ {
1180
+ if (is_string($rule)) {
1181
+ $rule = explode(',', $rule);
1182
+ }
1183
+ list($start, $end) = $rule;
1184
+ if (!is_numeric($start)) {
1185
+ $start = strtotime($start);
1186
+ }
1187
+
1188
+ if (!is_numeric($end)) {
1189
+ $end = strtotime($end);
1190
+ }
1191
+ return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end;
1192
+ }
1193
+
1194
+ /**
1195
+ * 验证IP许可
1196
+ * @access protected
1197
+ * @param string $value 字段值
1198
+ * @param mixed $rule 验证规则
1199
+ * @return mixed
1200
+ */
1201
+ protected function allowIp($value, $rule)
1202
+ {
1203
+ return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule));
1204
+ }
1205
+
1206
+ /**
1207
+ * 验证IP禁用
1208
+ * @access protected
1209
+ * @param string $value 字段值
1210
+ * @param mixed $rule 验证规则
1211
+ * @return mixed
1212
+ */
1213
+ protected function denyIp($value, $rule)
1214
+ {
1215
+ return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule));
1216
+ }
1217
+
1218
+ /**
1219
+ * 使用正则验证数据
1220
+ * @access protected
1221
+ * @param mixed $value 字段值
1222
+ * @param mixed $rule 验证规则 正则规则或者预定义正则名
1223
+ * @return mixed
1224
+ */
1225
+ protected function regex($value, $rule)
1226
+ {
1227
+ if (isset($this->regex[$rule])) {
1228
+ $rule = $this->regex[$rule];
1229
+ }
1230
+ if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
1231
+ // 不是正则表达式则两端补上/
1232
+ $rule = '/^' . $rule . '$/';
1233
+ }
1234
+ return is_scalar($value) && 1 === preg_match($rule, (string) $value);
1235
+ }
1236
+
1237
+ /**
1238
+ * 验证表单令牌
1239
+ * @access protected
1240
+ * @param mixed $value 字段值
1241
+ * @param mixed $rule 验证规则
1242
+ * @param array $data 数据
1243
+ * @return bool
1244
+ */
1245
+ protected function token($value, $rule, $data)
1246
+ {
1247
+ $rule = !empty($rule) ? $rule : '__token__';
1248
+ if (!isset($data[$rule]) || !Session::has($rule)) {
1249
+ // 令牌数据无效
1250
+ return false;
1251
+ }
1252
+
1253
+ // 令牌验证
1254
+ if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) {
1255
+ // 防止重复提交
1256
+ Session::delete($rule); // 验证完成销毁session
1257
+ return true;
1258
+ }
1259
+ // 开启TOKEN重置
1260
+ Session::delete($rule);
1261
+ return false;
1262
+ }
1263
+
1264
+ // 获取错误信息
1265
+ public function getError()
1266
+ {
1267
+ return $this->error;
1268
+ }
1269
+
1270
+ /**
1271
+ * 获取数据值
1272
+ * @access protected
1273
+ * @param array $data 数据
1274
+ * @param string $key 数据标识 支持二维
1275
+ * @return mixed
1276
+ */
1277
+ protected function getDataValue($data, $key)
1278
+ {
1279
+ if (is_numeric($key)) {
1280
+ $value = $key;
1281
+ } elseif (strpos($key, '.')) {
1282
+ // 支持二维数组验证
1283
+ list($name1, $name2) = explode('.', $key);
1284
+ $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null;
1285
+ } else {
1286
+ $value = isset($data[$key]) ? $data[$key] : null;
1287
+ }
1288
+ return $value;
1289
+ }
1290
+
1291
+ /**
1292
+ * 获取验证规则的错误提示信息
1293
+ * @access protected
1294
+ * @param string $attribute 字段英文名
1295
+ * @param string $title 字段描述名
1296
+ * @param string $type 验证规则名称
1297
+ * @param mixed $rule 验证规则数据
1298
+ * @return string
1299
+ */
1300
+ protected function getRuleMsg($attribute, $title, $type, $rule)
1301
+ {
1302
+ if (isset($this->message[$attribute . '.' . $type])) {
1303
+ $msg = $this->message[$attribute . '.' . $type];
1304
+ } elseif (isset($this->message[$attribute][$type])) {
1305
+ $msg = $this->message[$attribute][$type];
1306
+ } elseif (isset($this->message[$attribute])) {
1307
+ $msg = $this->message[$attribute];
1308
+ } elseif (isset(self::$typeMsg[$type])) {
1309
+ $msg = self::$typeMsg[$type];
1310
+ } elseif (0 === strpos($type, 'require')) {
1311
+ $msg = self::$typeMsg['require'];
1312
+ } else {
1313
+ $msg = $title . Lang::get('not conform to the rules');
1314
+ }
1315
+
1316
+ if (is_string($msg) && 0 === strpos($msg, '{%')) {
1317
+ $msg = Lang::get(substr($msg, 2, -1));
1318
+ } elseif (Lang::has($msg)) {
1319
+ $msg = Lang::get($msg);
1320
+ }
1321
+
1322
+ if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) {
1323
+ // 变量替换
1324
+ if (is_string($rule) && strpos($rule, ',')) {
1325
+ $array = array_pad(explode(',', $rule), 3, '');
1326
+ } else {
1327
+ $array = array_pad([], 3, '');
1328
+ }
1329
+ $msg = str_replace(
1330
+ [':attribute', ':rule', ':1', ':2', ':3'],
1331
+ [$title, (string) $rule, $array[0], $array[1], $array[2]],
1332
+ $msg);
1333
+ }
1334
+ return $msg;
1335
+ }
1336
+
1337
+ /**
1338
+ * 获取数据验证的场景
1339
+ * @access protected
1340
+ * @param string $scene 验证场景
1341
+ * @return array
1342
+ */
1343
+ protected function getScene($scene = '')
1344
+ {
1345
+ if (empty($scene)) {
1346
+ // 读取指定场景
1347
+ $scene = $this->currentScene;
1348
+ }
1349
+
1350
+ if (!empty($scene) && isset($this->scene[$scene])) {
1351
+ // 如果设置了验证适用场景
1352
+ $scene = $this->scene[$scene];
1353
+ if (is_string($scene)) {
1354
+ $scene = explode(',', $scene);
1355
+ }
1356
+ } else {
1357
+ $scene = [];
1358
+ }
1359
+ return $scene;
1360
+ }
1361
+
1362
+ public static function __callStatic($method, $params)
1363
+ {
1364
+ $class = self::make();
1365
+ if (method_exists($class, $method)) {
1366
+ return call_user_func_array([$class, $method], $params);
1367
+ } else {
1368
+ throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method);
1369
+ }
1370
+ }
1371
+ }
thinkphp/library/think/View.php ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think;
13
+
14
+ class View
15
+ {
16
+ // 视图实例
17
+ protected static $instance;
18
+ // 模板引擎实例
19
+ public $engine;
20
+ // 模板变量
21
+ protected $data = [];
22
+ // 用于静态赋值的模板变量
23
+ protected static $var = [];
24
+ // 视图输出替换
25
+ protected $replace = [];
26
+
27
+ /**
28
+ * 构造函数
29
+ * @access public
30
+ * @param array $engine 模板引擎参数
31
+ * @param array $replace 字符串替换参数
32
+ */
33
+ public function __construct($engine = [], $replace = [])
34
+ {
35
+ // 初始化模板引擎
36
+ $this->engine($engine);
37
+ // 基础替换字符串
38
+ $request = Request::instance();
39
+ $base = $request->root();
40
+ $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base;
41
+ if ('' != $root) {
42
+ $root = '/' . ltrim($root, '/');
43
+ }
44
+ // 如果 new_version 为 1 或者 new_version 不存在或者为null,则使用新版模板
45
+ if($GLOBALS['config']['site']['new_version'] == 1 || !isset($GLOBALS['config']['site']['new_version']) || (empty($GLOBALS['config']['site']['new_version']) && $GLOBALS['config']['site']['new_version'] != 0)){
46
+ $root . $static_path = '/static_new/';
47
+ }else{
48
+ $root . $static_path = '/static/';
49
+ }
50
+ $baseReplace = [
51
+ '__ROOT__' => $root,
52
+ 'MAC_BASE_PATH' => $root,
53
+ '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()),
54
+ '__STATIC__' => $root . $static_path,
55
+ '__CSS__' => $root . $static_path . '/css',
56
+ '__JS__' => $root . $static_path .'/js',
57
+ ];
58
+ $this->assign('MAC_BASE_PATH', $this->mac_base_path());
59
+ $this->replace = array_merge($baseReplace, (array) $replace);
60
+ }
61
+
62
+ public function mac_base_path()
63
+ {
64
+ return $GLOBALS['rootpath'];
65
+ }
66
+
67
+
68
+ /**
69
+ * 初始化视图
70
+ * @access public
71
+ * @param array $engine 模板引擎参数
72
+ * @param array $replace 字符串替换参数
73
+ * @return object
74
+ */
75
+ public static function instance($engine = [], $replace = [])
76
+ {
77
+ if (is_null(self::$instance)) {
78
+ self::$instance = new self($engine, $replace);
79
+ }
80
+ return self::$instance;
81
+ }
82
+
83
+ /**
84
+ * 模板变量静态赋值
85
+ * @access public
86
+ * @param mixed $name 变量名
87
+ * @param mixed $value 变量值
88
+ * @return void
89
+ */
90
+ public static function share($name, $value = '')
91
+ {
92
+ if (is_array($name)) {
93
+ self::$var = array_merge(self::$var, $name);
94
+ } else {
95
+ self::$var[$name] = $value;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 模板变量赋值
101
+ * @access public
102
+ * @param mixed $name 变量名
103
+ * @param mixed $value 变量值
104
+ * @return $this
105
+ */
106
+ public function assign($name, $value = '')
107
+ {
108
+ if (is_array($name)) {
109
+ $this->data = array_merge($this->data, $name);
110
+ } else {
111
+ $this->data[$name] = $value;
112
+ }
113
+ return $this;
114
+ }
115
+
116
+ /**
117
+ * 设置当前模板解析的引擎
118
+ * @access public
119
+ * @param array|string $options 引擎参数
120
+ * @return $this
121
+ */
122
+ public function engine($options = [])
123
+ {
124
+ if (is_string($options)) {
125
+ $type = $options;
126
+ $options = [];
127
+ } else {
128
+ $type = !empty($options['type']) ? $options['type'] : 'Think';
129
+ }
130
+
131
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type);
132
+ if (isset($options['type'])) {
133
+ unset($options['type']);
134
+ }
135
+ $this->engine = new $class($options);
136
+ return $this;
137
+ }
138
+
139
+ /**
140
+ * 配置模板引擎
141
+ * @access private
142
+ * @param string|array $name 参数名
143
+ * @param mixed $value 参数值
144
+ * @return $this
145
+ */
146
+ public function config($name, $value = null)
147
+ {
148
+ $this->engine->config($name, $value);
149
+ return $this;
150
+ }
151
+
152
+ /**
153
+ * 解析和获取模板内容 用于输出
154
+ * @param string $template 模板文件名或者内容
155
+ * @param array $vars 模板输出变量
156
+ * @param array $replace 替换内容
157
+ * @param array $config 模板参数
158
+ * @param bool $renderContent 是否渲染内容
159
+ * @return string
160
+ * @throws Exception
161
+ */
162
+ public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false)
163
+ {
164
+ // 模板变量
165
+ $vars = array_merge(self::$var, $this->data, $vars);
166
+
167
+ // 页面缓存
168
+ ob_start();
169
+ ob_implicit_flush(0);
170
+
171
+ // 渲染输出
172
+ try {
173
+ $method = $renderContent ? 'display' : 'fetch';
174
+ // 允许用户自定义模板的字符串替换
175
+ $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string'));
176
+ $this->engine->config('tpl_replace_string', $replace);
177
+ $this->engine->$method($template, $vars, $config);
178
+ } catch (\Exception $e) {
179
+ ob_end_clean();
180
+ throw $e;
181
+ }
182
+
183
+ // 获取并清空缓存
184
+ $content = ob_get_clean();
185
+ // 内容过滤标签
186
+ Hook::listen('view_filter', $content);
187
+ return $content;
188
+ }
189
+
190
+ /**
191
+ * 视图内容替换
192
+ * @access public
193
+ * @param string|array $content 被替换内容(支持批量替换)
194
+ * @param string $replace 替换内容
195
+ * @return $this
196
+ */
197
+ public function replace($content, $replace = '')
198
+ {
199
+ if (is_array($content)) {
200
+ $this->replace = array_merge($this->replace, $content);
201
+ } else {
202
+ $this->replace[$content] = $replace;
203
+ }
204
+ return $this;
205
+ }
206
+
207
+ /**
208
+ * 渲染内容输出
209
+ * @access public
210
+ * @param string $content 内容
211
+ * @param array $vars 模板输出变量
212
+ * @param array $replace 替换内容
213
+ * @param array $config 模板参数
214
+ * @return mixed
215
+ */
216
+ public function display($content, $vars = [], $replace = [], $config = [])
217
+ {
218
+ return $this->fetch($content, $vars, $replace, $config, true);
219
+ }
220
+
221
+ /**
222
+ * 模板变量赋值
223
+ * @access public
224
+ * @param string $name 变量名
225
+ * @param mixed $value 变量值
226
+ */
227
+ public function __set($name, $value)
228
+ {
229
+ $this->data[$name] = $value;
230
+ }
231
+
232
+ /**
233
+ * 取得模板显示变量的值
234
+ * @access protected
235
+ * @param string $name 模板变量
236
+ * @return mixed
237
+ */
238
+ public function __get($name)
239
+ {
240
+ return $this->data[$name];
241
+ }
242
+
243
+ /**
244
+ * 检测模板变量是否设置
245
+ * @access public
246
+ * @param string $name 模板变量名
247
+ * @return bool
248
+ */
249
+ public function __isset($name)
250
+ {
251
+ return isset($this->data[$name]);
252
+ }
253
+ }
thinkphp/library/think/cache/Driver.php ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache;
13
+
14
+ /**
15
+ * 缓存基础类
16
+ */
17
+ abstract class Driver
18
+ {
19
+ protected $handler = null;
20
+ protected $options = [];
21
+ protected $tag;
22
+
23
+ /**
24
+ * 判断缓存是否存在
25
+ * @access public
26
+ * @param string $name 缓存变量名
27
+ * @return bool
28
+ */
29
+ abstract public function has($name);
30
+
31
+ /**
32
+ * 读取缓存
33
+ * @access public
34
+ * @param string $name 缓存变量名
35
+ * @param mixed $default 默认值
36
+ * @return mixed
37
+ */
38
+ abstract public function get($name, $default = false);
39
+
40
+ /**
41
+ * 写入缓存
42
+ * @access public
43
+ * @param string $name 缓存变量名
44
+ * @param mixed $value 存储数据
45
+ * @param int $expire 有效时间 0为永久
46
+ * @return boolean
47
+ */
48
+ abstract public function set($name, $value, $expire = null);
49
+
50
+ /**
51
+ * 自增缓存(针对数值缓存)
52
+ * @access public
53
+ * @param string $name 缓存变量名
54
+ * @param int $step 步长
55
+ * @return false|int
56
+ */
57
+ abstract public function inc($name, $step = 1);
58
+
59
+ /**
60
+ * 自减缓存(针对数值缓存)
61
+ * @access public
62
+ * @param string $name 缓存变量名
63
+ * @param int $step 步长
64
+ * @return false|int
65
+ */
66
+ abstract public function dec($name, $step = 1);
67
+
68
+ /**
69
+ * 删除缓存
70
+ * @access public
71
+ * @param string $name 缓存变量名
72
+ * @return boolean
73
+ */
74
+ abstract public function rm($name);
75
+
76
+ /**
77
+ * 清除缓存
78
+ * @access public
79
+ * @param string $tag 标签名
80
+ * @return boolean
81
+ */
82
+ abstract public function clear($tag = null);
83
+
84
+ /**
85
+ * 获取实际的缓存标识
86
+ * @access public
87
+ * @param string $name 缓存名
88
+ * @return string
89
+ */
90
+ protected function getCacheKey($name)
91
+ {
92
+ return $this->options['prefix'] . $name;
93
+ }
94
+
95
+ /**
96
+ * 读取缓存并删除
97
+ * @access public
98
+ * @param string $name 缓存变量名
99
+ * @return mixed
100
+ */
101
+ public function pull($name)
102
+ {
103
+ $result = $this->get($name, false);
104
+ if ($result) {
105
+ $this->rm($name);
106
+ return $result;
107
+ } else {
108
+ return;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 如果不存在则写入缓存
114
+ * @access public
115
+ * @param string $name 缓存变量名
116
+ * @param mixed $value 存储数据
117
+ * @param int $expire 有效时间 0为永久
118
+ * @return mixed
119
+ */
120
+ public function remember($name, $value, $expire = null)
121
+ {
122
+ if (!$this->has($name)) {
123
+ $time = time();
124
+ while ($time + 5 > time() && $this->has($name . '_lock')) {
125
+ // 存在锁定则等待
126
+ usleep(200000);
127
+ }
128
+
129
+ try {
130
+ // 锁定
131
+ $this->set($name . '_lock', true);
132
+ if ($value instanceof \Closure) {
133
+ $value = call_user_func($value);
134
+ }
135
+ $this->set($name, $value, $expire);
136
+ // 解锁
137
+ $this->rm($name . '_lock');
138
+ } catch (\Exception $e) {
139
+ // 解锁
140
+ $this->rm($name . '_lock');
141
+ throw $e;
142
+ } catch (\throwable $e) {
143
+ $this->rm($name . '_lock');
144
+ throw $e;
145
+ }
146
+ } else {
147
+ $value = $this->get($name);
148
+ }
149
+ return $value;
150
+ }
151
+
152
+ /**
153
+ * 缓存标签
154
+ * @access public
155
+ * @param string $name 标签名
156
+ * @param string|array $keys 缓存标识
157
+ * @param bool $overlay 是否覆盖
158
+ * @return $this
159
+ */
160
+ public function tag($name, $keys = null, $overlay = false)
161
+ {
162
+ if (is_null($name)) {
163
+
164
+ } elseif (is_null($keys)) {
165
+ $this->tag = $name;
166
+ } else {
167
+ $key = 'tag_' . md5($name);
168
+ if (is_string($keys)) {
169
+ $keys = explode(',', $keys);
170
+ }
171
+ $keys = array_map([$this, 'getCacheKey'], $keys);
172
+ if ($overlay) {
173
+ $value = $keys;
174
+ } else {
175
+ $value = array_unique(array_merge($this->getTagItem($name), $keys));
176
+ }
177
+ $this->set($key, implode(',', $value), 0);
178
+ }
179
+ return $this;
180
+ }
181
+
182
+ /**
183
+ * 更新标签
184
+ * @access public
185
+ * @param string $name 缓存标识
186
+ * @return void
187
+ */
188
+ protected function setTagItem($name)
189
+ {
190
+ if ($this->tag) {
191
+ $key = 'tag_' . md5($this->tag);
192
+ $this->tag = null;
193
+ if ($this->has($key)) {
194
+ $value = explode(',', $this->get($key));
195
+ $value[] = $name;
196
+ $value = implode(',', array_unique($value));
197
+ } else {
198
+ $value = $name;
199
+ }
200
+ $this->set($key, $value, 0);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * 获取标签包含的缓存标识
206
+ * @access public
207
+ * @param string $tag 缓存标签
208
+ * @return array
209
+ */
210
+ protected function getTagItem($tag)
211
+ {
212
+ $key = 'tag_' . md5($tag);
213
+ $value = $this->get($key);
214
+ if ($value) {
215
+ return array_filter(explode(',', $value));
216
+ } else {
217
+ return [];
218
+ }
219
+ }
220
+
221
+ /**
222
+ * 返回句柄对象,可执行其它高级方法
223
+ *
224
+ * @access public
225
+ * @return object
226
+ */
227
+ public function handler()
228
+ {
229
+ return $this->handler;
230
+ }
231
+ }
thinkphp/library/think/cache/driver/File.php ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache\driver;
13
+
14
+ use think\cache\Driver;
15
+
16
+ /**
17
+ * 文件类型缓存类
18
+ * @author liu21st <liu21st@gmail.com>
19
+ */
20
+ class File extends Driver
21
+ {
22
+ protected $options = [
23
+ 'expire' => 0,
24
+ 'cache_subdir' => true,
25
+ 'prefix' => '',
26
+ 'path' => CACHE_PATH,
27
+ 'data_compress' => false,
28
+ ];
29
+
30
+ protected $expire;
31
+
32
+ /**
33
+ * 构造函数
34
+ * @param array $options
35
+ */
36
+ public function __construct($options = [])
37
+ {
38
+ if (!empty($options)) {
39
+ $this->options = array_merge($this->options, $options);
40
+ }
41
+ if (substr($this->options['path'], -1) != DS) {
42
+ $this->options['path'] .= DS;
43
+ }
44
+ $this->init();
45
+ }
46
+
47
+ /**
48
+ * 初始化检查
49
+ * @access private
50
+ * @return boolean
51
+ */
52
+ private function init()
53
+ {
54
+ // 创建项目缓存目录
55
+ if (!is_dir($this->options['path'])) {
56
+ if (mkdir($this->options['path'], 0755, true)) {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+
63
+ /**
64
+ * 取得变量的存储文件名
65
+ * @access protected
66
+ * @param string $name 缓存变量名
67
+ * @param bool $auto 是否自动创建目录
68
+ * @return string
69
+ */
70
+ protected function getCacheKey($name, $auto = false)
71
+ {
72
+ $name = md5($name);
73
+ if ($this->options['cache_subdir']) {
74
+ // 使用子目录
75
+ $name = substr($name, 0, 2) . DS . substr($name, 2);
76
+ }
77
+ if ($this->options['prefix']) {
78
+ $name = $this->options['prefix'] . DS . $name;
79
+ }
80
+ $filename = $this->options['path'] . $name . '.php';
81
+ $dir = dirname($filename);
82
+
83
+ if ($auto && !is_dir($dir)) {
84
+ mkdir($dir, 0755, true);
85
+ }
86
+ return $filename;
87
+ }
88
+
89
+ /**
90
+ * 判断缓存是否存在
91
+ * @access public
92
+ * @param string $name 缓存变量名
93
+ * @return bool
94
+ */
95
+ public function has($name)
96
+ {
97
+ return $this->get($name) ? true : false;
98
+ }
99
+
100
+ /**
101
+ * 读取缓存
102
+ * @access public
103
+ * @param string $name 缓存变量名
104
+ * @param mixed $default 默认值
105
+ * @return mixed
106
+ */
107
+ public function get($name, $default = false)
108
+ {
109
+ $filename = $this->getCacheKey($name);
110
+ if (!is_file($filename)) {
111
+ return $default;
112
+ }
113
+ $content = file_get_contents($filename);
114
+ $this->expire = null;
115
+ if (false !== $content) {
116
+ $expire = (int) substr($content, 8, 12);
117
+ if (0 != $expire && time() > filemtime($filename) + $expire) {
118
+ return $default;
119
+ }
120
+ $this->expire = $expire;
121
+ $content = substr($content, 32);
122
+ if ($this->options['data_compress'] && function_exists('gzcompress')) {
123
+ //启用数据压缩
124
+ $content = gzuncompress($content);
125
+ }
126
+ $content = unserialize($content);
127
+ return $content;
128
+ } else {
129
+ return $default;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 写入缓存
135
+ * @access public
136
+ * @param string $name 缓存变量名
137
+ * @param mixed $value 存储数据
138
+ * @param integer|\DateTime $expire 有效时间(秒)
139
+ * @return boolean
140
+ */
141
+ public function set($name, $value, $expire = null)
142
+ {
143
+ if (is_null($expire)) {
144
+ $expire = $this->options['expire'];
145
+ }
146
+ if ($expire instanceof \DateTime) {
147
+ $expire = $expire->getTimestamp() - time();
148
+ }
149
+ $filename = $this->getCacheKey($name, true);
150
+ if ($this->tag && !is_file($filename)) {
151
+ $first = true;
152
+ }
153
+ $data = serialize($value);
154
+ if ($this->options['data_compress'] && function_exists('gzcompress')) {
155
+ //数据压缩
156
+ $data = gzcompress($data, 3);
157
+ }
158
+ $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
159
+ $result = file_put_contents($filename, $data);
160
+ if ($result) {
161
+ isset($first) && $this->setTagItem($filename);
162
+ clearstatcache();
163
+ return true;
164
+ } else {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 自��缓存(针对数值缓存)
171
+ * @access public
172
+ * @param string $name 缓存变量名
173
+ * @param int $step 步长
174
+ * @return false|int
175
+ */
176
+ public function inc($name, $step = 1)
177
+ {
178
+ if ($this->has($name)) {
179
+ $value = $this->get($name) + $step;
180
+ $expire = $this->expire;
181
+ } else {
182
+ $value = $step;
183
+ $expire = 0;
184
+ }
185
+
186
+ return $this->set($name, $value, $expire) ? $value : false;
187
+ }
188
+
189
+ /**
190
+ * 自减缓存(针对数值缓存)
191
+ * @access public
192
+ * @param string $name 缓存变量名
193
+ * @param int $step 步长
194
+ * @return false|int
195
+ */
196
+ public function dec($name, $step = 1)
197
+ {
198
+ if ($this->has($name)) {
199
+ $value = $this->get($name) - $step;
200
+ $expire = $this->expire;
201
+ } else {
202
+ $value = -$step;
203
+ $expire = 0;
204
+ }
205
+
206
+ return $this->set($name, $value, $expire) ? $value : false;
207
+ }
208
+
209
+ /**
210
+ * 删除缓存
211
+ * @access public
212
+ * @param string $name 缓存变量名
213
+ * @return boolean
214
+ */
215
+ public function rm($name)
216
+ {
217
+ $filename = $this->getCacheKey($name);
218
+ try {
219
+ return $this->unlink($filename);
220
+ } catch (\Exception $e) {
221
+ }
222
+ }
223
+
224
+ /**
225
+ * 清除缓存
226
+ * @access public
227
+ * @param string $tag 标签名
228
+ * @return boolean
229
+ */
230
+ public function clear($tag = null)
231
+ {
232
+ if ($tag) {
233
+ // 指定标签清除
234
+ $keys = $this->getTagItem($tag);
235
+ foreach ($keys as $key) {
236
+ $this->unlink($key);
237
+ }
238
+ $this->rm('tag_' . md5($tag));
239
+ return true;
240
+ }
241
+ $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*');
242
+ foreach ($files as $path) {
243
+ if (is_dir($path)) {
244
+ $matches = glob($path . '/*.php');
245
+ if (is_array($matches)) {
246
+ array_map('unlink', $matches);
247
+ }
248
+ rmdir($path);
249
+ } else {
250
+ unlink($path);
251
+ }
252
+ }
253
+ return true;
254
+ }
255
+
256
+ /**
257
+ * 判断文件是否存在后,删除
258
+ * @param $path
259
+ * @return bool
260
+ * @author byron sampson <xiaobo.sun@qq.com>
261
+ * @return boolean
262
+ */
263
+ private function unlink($path)
264
+ {
265
+ return is_file($path) && unlink($path);
266
+ }
267
+
268
+ }
thinkphp/library/think/cache/driver/Lite.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache\driver;
13
+
14
+ use think\cache\Driver;
15
+
16
+ /**
17
+ * 文件类型缓存类
18
+ * @author liu21st <liu21st@gmail.com>
19
+ */
20
+ class Lite extends Driver
21
+ {
22
+ protected $options = [
23
+ 'prefix' => '',
24
+ 'path' => '',
25
+ 'expire' => 0, // 等于 10*365*24*3600(10年)
26
+ ];
27
+
28
+ /**
29
+ * 构造函数
30
+ * @access public
31
+ *
32
+ * @param array $options
33
+ */
34
+ public function __construct($options = [])
35
+ {
36
+ if (!empty($options)) {
37
+ $this->options = array_merge($this->options, $options);
38
+ }
39
+ if (substr($this->options['path'], -1) != DS) {
40
+ $this->options['path'] .= DS;
41
+ }
42
+
43
+ }
44
+
45
+ /**
46
+ * 取得变量的存储文件名
47
+ * @access protected
48
+ * @param string $name 缓存变量名
49
+ * @return string
50
+ */
51
+ protected function getCacheKey($name)
52
+ {
53
+ return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php';
54
+ }
55
+
56
+ /**
57
+ * 判断缓存是否存在
58
+ * @access public
59
+ * @param string $name 缓存变量名
60
+ * @return mixed
61
+ */
62
+ public function has($name)
63
+ {
64
+ return $this->get($name) ? true : false;
65
+ }
66
+
67
+ /**
68
+ * 读取缓存
69
+ * @access public
70
+ * @param string $name 缓存变量名
71
+ * @param mixed $default 默认值
72
+ * @return mixed
73
+ */
74
+ public function get($name, $default = false)
75
+ {
76
+ $filename = $this->getCacheKey($name);
77
+ if (is_file($filename)) {
78
+ // 判断是否过期
79
+ $mtime = filemtime($filename);
80
+ if ($mtime < time()) {
81
+ // 清除已经过期的文件
82
+ unlink($filename);
83
+ return $default;
84
+ }
85
+ return include $filename;
86
+ } else {
87
+ return $default;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 写入缓存
93
+ * @access public
94
+ * @param string $name 缓存变量名
95
+ * @param mixed $value 存储数据
96
+ * @param integer|\DateTime $expire 有效时间(秒)
97
+ * @return bool
98
+ */
99
+ public function set($name, $value, $expire = null)
100
+ {
101
+ if (is_null($expire)) {
102
+ $expire = $this->options['expire'];
103
+ }
104
+ if ($expire instanceof \DateTime) {
105
+ $expire = $expire->getTimestamp();
106
+ } else {
107
+ $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire;
108
+ $expire = time() + $expire;
109
+ }
110
+ $filename = $this->getCacheKey($name);
111
+ if ($this->tag && !is_file($filename)) {
112
+ $first = true;
113
+ }
114
+ $ret = file_put_contents($filename, ("<?php return " . var_export($value, true) . ";"));
115
+ // 通过设置修改时间实现有效期
116
+ if ($ret) {
117
+ isset($first) && $this->setTagItem($filename);
118
+ touch($filename, $expire);
119
+ }
120
+ return $ret;
121
+ }
122
+
123
+ /**
124
+ * 自增缓存(针对数值缓存)
125
+ * @access public
126
+ * @param string $name 缓存变量名
127
+ * @param int $step 步长
128
+ * @return false|int
129
+ */
130
+ public function inc($name, $step = 1)
131
+ {
132
+ if ($this->has($name)) {
133
+ $value = $this->get($name) + $step;
134
+ } else {
135
+ $value = $step;
136
+ }
137
+ return $this->set($name, $value, 0) ? $value : false;
138
+ }
139
+
140
+ /**
141
+ * 自减缓存(针对数值缓存)
142
+ * @access public
143
+ * @param string $name 缓存变量名
144
+ * @param int $step 步长
145
+ * @return false|int
146
+ */
147
+ public function dec($name, $step = 1)
148
+ {
149
+ if ($this->has($name)) {
150
+ $value = $this->get($name) - $step;
151
+ } else {
152
+ $value = -$step;
153
+ }
154
+ return $this->set($name, $value, 0) ? $value : false;
155
+ }
156
+
157
+ /**
158
+ * 删除缓存
159
+ * @access public
160
+ * @param string $name 缓存变量名
161
+ * @return boolean
162
+ */
163
+ public function rm($name)
164
+ {
165
+ return unlink($this->getCacheKey($name));
166
+ }
167
+
168
+ /**
169
+ * 清除缓存
170
+ * @access public
171
+ * @param string $tag 标签名
172
+ * @return bool
173
+ */
174
+ public function clear($tag = null)
175
+ {
176
+ if ($tag) {
177
+ // 指定标签清除
178
+ $keys = $this->getTagItem($tag);
179
+ foreach ($keys as $key) {
180
+ unlink($key);
181
+ }
182
+ $this->rm('tag_' . md5($tag));
183
+ return true;
184
+ }
185
+ array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php'));
186
+ }
187
+ }
thinkphp/library/think/cache/driver/Memcache.php ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache\driver;
13
+
14
+ use think\cache\Driver;
15
+
16
+ class Memcache extends Driver
17
+ {
18
+ protected $options = [
19
+ 'host' => '127.0.0.1',
20
+ 'port' => 11211,
21
+ 'expire' => 0,
22
+ 'timeout' => 0, // 超时时间(单位:毫秒)
23
+ 'persistent' => true,
24
+ 'prefix' => '',
25
+ ];
26
+
27
+ /**
28
+ * 构造函数
29
+ * @param array $options 缓存参数
30
+ * @access public
31
+ * @throws \BadFunctionCallException
32
+ */
33
+ public function __construct($options = [])
34
+ {
35
+ if (!extension_loaded('memcache')) {
36
+ throw new \BadFunctionCallException('not support: memcache');
37
+ }
38
+ if (!empty($options)) {
39
+ $this->options = array_merge($this->options, $options);
40
+ }
41
+ $this->handler = new \Memcache;
42
+ // 支持集群
43
+ $hosts = explode(',', $this->options['host']);
44
+ $ports = explode(',', $this->options['port']);
45
+ if (empty($ports[0])) {
46
+ $ports[0] = 11211;
47
+ }
48
+ // 建立连接
49
+ foreach ((array) $hosts as $i => $host) {
50
+ $port = isset($ports[$i]) ? $ports[$i] : $ports[0];
51
+ $this->options['timeout'] > 0 ?
52
+ $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) :
53
+ $this->handler->addServer($host, $port, $this->options['persistent'], 1);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * 判断缓存
59
+ * @access public
60
+ * @param string $name 缓存变量名
61
+ * @return bool
62
+ */
63
+ public function has($name)
64
+ {
65
+ $key = $this->getCacheKey($name);
66
+ return false !== $this->handler->get($key);
67
+ }
68
+
69
+ /**
70
+ * 读取缓存
71
+ * @access public
72
+ * @param string $name 缓存变量名
73
+ * @param mixed $default 默认值
74
+ * @return mixed
75
+ */
76
+ public function get($name, $default = false)
77
+ {
78
+ $result = $this->handler->get($this->getCacheKey($name));
79
+ return false !== $result ? $result : $default;
80
+ }
81
+
82
+ /**
83
+ * 写入缓存
84
+ * @access public
85
+ * @param string $name 缓存变量名
86
+ * @param mixed $value 存储数据
87
+ * @param integer|\DateTime $expire 有效时间(秒)
88
+ * @return bool
89
+ */
90
+ public function set($name, $value, $expire = null)
91
+ {
92
+ if (is_null($expire)) {
93
+ $expire = $this->options['expire'];
94
+ }
95
+ if ($expire instanceof \DateTime) {
96
+ $expire = $expire->getTimestamp() - time();
97
+ }
98
+ if ($this->tag && !$this->has($name)) {
99
+ $first = true;
100
+ }
101
+ $key = $this->getCacheKey($name);
102
+ if ($this->handler->set($key, $value, 0, $expire)) {
103
+ isset($first) && $this->setTagItem($key);
104
+ return true;
105
+ }
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * 自增缓存(针对数值缓存)
111
+ * @access public
112
+ * @param string $name 缓存变量名
113
+ * @param int $step 步长
114
+ * @return false|int
115
+ */
116
+ public function inc($name, $step = 1)
117
+ {
118
+ $key = $this->getCacheKey($name);
119
+ if ($this->handler->get($key)) {
120
+ return $this->handler->increment($key, $step);
121
+ }
122
+ return $this->handler->set($key, $step);
123
+ }
124
+
125
+ /**
126
+ * 自减缓存(针对数值缓存)
127
+ * @access public
128
+ * @param string $name 缓存变量名
129
+ * @param int $step 步长
130
+ * @return false|int
131
+ */
132
+ public function dec($name, $step = 1)
133
+ {
134
+ $key = $this->getCacheKey($name);
135
+ $value = $this->handler->get($key) - $step;
136
+ $res = $this->handler->set($key, $value);
137
+ if (!$res) {
138
+ return false;
139
+ } else {
140
+ return $value;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 删除缓存
146
+ * @param string $name 缓存变量名
147
+ * @param bool|false $ttl
148
+ * @return bool
149
+ */
150
+ public function rm($name, $ttl = false)
151
+ {
152
+ $key = $this->getCacheKey($name);
153
+ return false === $ttl ?
154
+ $this->handler->delete($key) :
155
+ $this->handler->delete($key, $ttl);
156
+ }
157
+
158
+ /**
159
+ * 清除缓存
160
+ * @access public
161
+ * @param string $tag 标签名
162
+ * @return bool
163
+ */
164
+ public function clear($tag = null)
165
+ {
166
+ if ($tag) {
167
+ // 指定标签清除
168
+ $keys = $this->getTagItem($tag);
169
+ foreach ($keys as $key) {
170
+ $this->handler->delete($key);
171
+ }
172
+ $this->rm('tag_' . md5($tag));
173
+ return true;
174
+ }
175
+ return $this->handler->flush();
176
+ }
177
+ }
thinkphp/library/think/cache/driver/Memcached.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache\driver;
13
+
14
+ use think\cache\Driver;
15
+
16
+ class Memcached extends Driver
17
+ {
18
+ protected $options = [
19
+ 'host' => '127.0.0.1',
20
+ 'port' => 11211,
21
+ 'expire' => 0,
22
+ 'timeout' => 0, // 超时时间(单位:毫秒)
23
+ 'prefix' => '',
24
+ 'username' => '', //账号
25
+ 'password' => '', //密码
26
+ 'option' => [],
27
+ ];
28
+
29
+ /**
30
+ * 构造函数
31
+ * @param array $options 缓存参数
32
+ * @access public
33
+ */
34
+ public function __construct($options = [])
35
+ {
36
+ if (!extension_loaded('memcached')) {
37
+ throw new \BadFunctionCallException('not support: memcached');
38
+ }
39
+ if (!empty($options)) {
40
+ $this->options = array_merge($this->options, $options);
41
+ }
42
+ $this->handler = new \Memcached;
43
+ if (!empty($this->options['option'])) {
44
+ $this->handler->setOptions($this->options['option']);
45
+ }
46
+ // 设置连接超时时间(单位:毫秒)
47
+ if ($this->options['timeout'] > 0) {
48
+ $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']);
49
+ }
50
+ // 支持集群
51
+ $hosts = explode(',', $this->options['host']);
52
+ $ports = explode(',', $this->options['port']);
53
+ if (empty($ports[0])) {
54
+ $ports[0] = 11211;
55
+ }
56
+ // 建立连接
57
+ $servers = [];
58
+ foreach ((array) $hosts as $i => $host) {
59
+ $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1];
60
+ }
61
+ $this->handler->addServers($servers);
62
+ if ('' != $this->options['username']) {
63
+ $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
64
+ $this->handler->setSaslAuthData($this->options['username'], $this->options['password']);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 判断缓存
70
+ * @access public
71
+ * @param string $name 缓存变量名
72
+ * @return bool
73
+ */
74
+ public function has($name)
75
+ {
76
+ $key = $this->getCacheKey($name);
77
+ return $this->handler->get($key) ? true : false;
78
+ }
79
+
80
+ /**
81
+ * 读取缓存
82
+ * @access public
83
+ * @param string $name 缓存变量名
84
+ * @param mixed $default 默认值
85
+ * @return mixed
86
+ */
87
+ public function get($name, $default = false)
88
+ {
89
+ $result = $this->handler->get($this->getCacheKey($name));
90
+ return false !== $result ? $result : $default;
91
+ }
92
+
93
+ /**
94
+ * 写入缓存
95
+ * @access public
96
+ * @param string $name 缓存变量名
97
+ * @param mixed $value 存储数据
98
+ * @param integer|\DateTime $expire 有效时间(秒)
99
+ * @return bool
100
+ */
101
+ public function set($name, $value, $expire = null)
102
+ {
103
+ if (is_null($expire)) {
104
+ $expire = $this->options['expire'];
105
+ }
106
+ if ($expire instanceof \DateTime) {
107
+ $expire = $expire->getTimestamp() - time();
108
+ }
109
+ if ($this->tag && !$this->has($name)) {
110
+ $first = true;
111
+ }
112
+ $key = $this->getCacheKey($name);
113
+ $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire;
114
+ if ($this->handler->set($key, $value, $expire)) {
115
+ isset($first) && $this->setTagItem($key);
116
+ return true;
117
+ }
118
+ return false;
119
+ }
120
+
121
+ /**
122
+ * 自增缓存(针对数值缓存)
123
+ * @access public
124
+ * @param string $name 缓存变量名
125
+ * @param int $step 步长
126
+ * @return false|int
127
+ */
128
+ public function inc($name, $step = 1)
129
+ {
130
+ $key = $this->getCacheKey($name);
131
+ if ($this->handler->get($key)) {
132
+ return $this->handler->increment($key, $step);
133
+ }
134
+ return $this->handler->set($key, $step);
135
+ }
136
+
137
+ /**
138
+ * 自减缓存(针对数值缓存)
139
+ * @access public
140
+ * @param string $name 缓存变量名
141
+ * @param int $step 步长
142
+ * @return false|int
143
+ */
144
+ public function dec($name, $step = 1)
145
+ {
146
+ $key = $this->getCacheKey($name);
147
+ $value = $this->handler->get($key) - $step;
148
+ $res = $this->handler->set($key, $value);
149
+ if (!$res) {
150
+ return false;
151
+ } else {
152
+ return $value;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * 删除缓存
158
+ * @param string $name 缓存变量名
159
+ * @param bool|false $ttl
160
+ * @return bool
161
+ */
162
+ public function rm($name, $ttl = false)
163
+ {
164
+ $key = $this->getCacheKey($name);
165
+ return false === $ttl ?
166
+ $this->handler->delete($key) :
167
+ $this->handler->delete($key, $ttl);
168
+ }
169
+
170
+ /**
171
+ * 清除缓存
172
+ * @access public
173
+ * @param string $tag 标签名
174
+ * @return bool
175
+ */
176
+ public function clear($tag = null)
177
+ {
178
+ if ($tag) {
179
+ // 指定标签清除
180
+ $keys = $this->getTagItem($tag);
181
+ $this->handler->deleteMulti($keys);
182
+ $this->rm('tag_' . md5($tag));
183
+ return true;
184
+ }
185
+ return $this->handler->flush();
186
+ }
187
+ }
thinkphp/library/think/cache/driver/Redis.php ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache\driver;
13
+
14
+ use think\cache\Driver;
15
+
16
+ /**
17
+ * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好
18
+ * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动
19
+ *
20
+ * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis
21
+ * @author 尘缘 <130775@qq.com>
22
+ */
23
+ class Redis extends Driver
24
+ {
25
+ protected $options = [
26
+ 'host' => '127.0.0.1',
27
+ 'port' => 6379,
28
+ 'password' => '',
29
+ 'select' => 0,
30
+ 'timeout' => 0,
31
+ 'expire' => 0,
32
+ 'persistent' => false,
33
+ 'prefix' => '',
34
+ ];
35
+
36
+ /**
37
+ * 构造函数
38
+ * @param array $options 缓存参数
39
+ * @access public
40
+ */
41
+ public function __construct($options = [])
42
+ {
43
+ if (!extension_loaded('redis')) {
44
+ throw new \BadFunctionCallException('not support: redis');
45
+ }
46
+ if (!empty($options)) {
47
+ $this->options = array_merge($this->options, $options);
48
+ }
49
+ $this->handler = new \Redis;
50
+ if ($this->options['persistent']) {
51
+ $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']);
52
+ } else {
53
+ $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
54
+ }
55
+
56
+ if ('' != $this->options['password']) {
57
+ $this->handler->auth($this->options['password']);
58
+ }
59
+
60
+ if (0 != $this->options['select']) {
61
+ $this->handler->select($this->options['select']);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * 判断缓存
67
+ * @access public
68
+ * @param string $name 缓存变量名
69
+ * @return bool
70
+ */
71
+ public function has($name)
72
+ {
73
+ return $this->handler->exists($this->getCacheKey($name));
74
+ }
75
+
76
+ /**
77
+ * 读取缓存
78
+ * @access public
79
+ * @param string $name 缓存变量名
80
+ * @param mixed $default 默认值
81
+ * @return mixed
82
+ */
83
+ public function get($name, $default = false)
84
+ {
85
+ $value = $this->handler->get($this->getCacheKey($name));
86
+ if (is_null($value) || false === $value) {
87
+ return $default;
88
+ }
89
+
90
+ try {
91
+ $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value;
92
+ } catch (\Exception $e) {
93
+ $result = $default;
94
+ }
95
+
96
+ return $result;
97
+ }
98
+
99
+ /**
100
+ * 写入缓存
101
+ * @access public
102
+ * @param string $name 缓存变量名
103
+ * @param mixed $value 存储数据
104
+ * @param integer|\DateTime $expire 有效时间(秒)
105
+ * @return boolean
106
+ */
107
+ public function set($name, $value, $expire = null)
108
+ {
109
+ if (is_null($expire)) {
110
+ $expire = $this->options['expire'];
111
+ }
112
+ if ($expire instanceof \DateTime) {
113
+ $expire = $expire->getTimestamp() - time();
114
+ }
115
+ if ($this->tag && !$this->has($name)) {
116
+ $first = true;
117
+ }
118
+ $key = $this->getCacheKey($name);
119
+ $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value);
120
+ if ($expire) {
121
+ $result = $this->handler->setex($key, $expire, $value);
122
+ } else {
123
+ $result = $this->handler->set($key, $value);
124
+ }
125
+ isset($first) && $this->setTagItem($key);
126
+ return $result;
127
+ }
128
+
129
+ /**
130
+ * 自增缓存(针对数值缓存)
131
+ * @access public
132
+ * @param string $name 缓存变量名
133
+ * @param int $step 步长
134
+ * @return false|int
135
+ */
136
+ public function inc($name, $step = 1)
137
+ {
138
+ $key = $this->getCacheKey($name);
139
+
140
+ return $this->handler->incrby($key, $step);
141
+ }
142
+
143
+ /**
144
+ * 自减缓存(针对数值缓存)
145
+ * @access public
146
+ * @param string $name 缓存变量名
147
+ * @param int $step 步长
148
+ * @return false|int
149
+ */
150
+ public function dec($name, $step = 1)
151
+ {
152
+ $key = $this->getCacheKey($name);
153
+
154
+ return $this->handler->decrby($key, $step);
155
+ }
156
+
157
+ /**
158
+ * 删除缓存
159
+ * @access public
160
+ * @param string $name 缓存变量名
161
+ * @return boolean
162
+ */
163
+ public function rm($name)
164
+ {
165
+ return $this->handler->delete($this->getCacheKey($name));
166
+ }
167
+
168
+ /**
169
+ * 清除缓存
170
+ * @access public
171
+ * @param string $tag 标签名
172
+ * @return boolean
173
+ */
174
+ public function clear($tag = null)
175
+ {
176
+ if ($tag) {
177
+ // 指定标签清除
178
+ $keys = $this->getTagItem($tag);
179
+ foreach ($keys as $key) {
180
+ $this->handler->delete($key);
181
+ }
182
+ $this->rm('tag_' . md5($tag));
183
+ return true;
184
+ }
185
+ return $this->handler->flushDB();
186
+ }
187
+
188
+ }
thinkphp/library/think/cache/driver/Sqlite.php ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache\driver;
13
+
14
+ use think\cache\Driver;
15
+
16
+ /**
17
+ * Sqlite缓存驱动
18
+ * @author liu21st <liu21st@gmail.com>
19
+ */
20
+ class Sqlite extends Driver
21
+ {
22
+ protected $options = [
23
+ 'db' => ':memory:',
24
+ 'table' => 'sharedmemory',
25
+ 'prefix' => '',
26
+ 'expire' => 0,
27
+ 'persistent' => false,
28
+ ];
29
+
30
+ /**
31
+ * 构造函数
32
+ * @param array $options 缓存参数
33
+ * @throws \BadFunctionCallException
34
+ * @access public
35
+ */
36
+ public function __construct($options = [])
37
+ {
38
+ if (!extension_loaded('sqlite')) {
39
+ throw new \BadFunctionCallException('not support: sqlite');
40
+ }
41
+ if (!empty($options)) {
42
+ $this->options = array_merge($this->options, $options);
43
+ }
44
+ $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open';
45
+ $this->handler = $func($this->options['db']);
46
+ }
47
+
48
+ /**
49
+ * 获取实际的缓存标识
50
+ * @access public
51
+ * @param string $name 缓存名
52
+ * @return string
53
+ */
54
+ protected function getCacheKey($name)
55
+ {
56
+ return $this->options['prefix'] . sqlite_escape_string($name);
57
+ }
58
+
59
+ /**
60
+ * 判断缓存
61
+ * @access public
62
+ * @param string $name 缓存变量名
63
+ * @return bool
64
+ */
65
+ public function has($name)
66
+ {
67
+ $name = $this->getCacheKey($name);
68
+ $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1';
69
+ $result = sqlite_query($this->handler, $sql);
70
+ return sqlite_num_rows($result);
71
+ }
72
+
73
+ /**
74
+ * 读取缓存
75
+ * @access public
76
+ * @param string $name 缓存变量名
77
+ * @param mixed $default 默认值
78
+ * @return mixed
79
+ */
80
+ public function get($name, $default = false)
81
+ {
82
+ $name = $this->getCacheKey($name);
83
+ $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1';
84
+ $result = sqlite_query($this->handler, $sql);
85
+ if (sqlite_num_rows($result)) {
86
+ $content = sqlite_fetch_single($result);
87
+ if (function_exists('gzcompress')) {
88
+ //启用数据压缩
89
+ $content = gzuncompress($content);
90
+ }
91
+ return unserialize($content);
92
+ }
93
+ return $default;
94
+ }
95
+
96
+ /**
97
+ * 写入缓存
98
+ * @access public
99
+ * @param string $name 缓存变量名
100
+ * @param mixed $value 存储数据
101
+ * @param integer|\DateTime $expire 有效时间(秒)
102
+ * @return boolean
103
+ */
104
+ public function set($name, $value, $expire = null)
105
+ {
106
+ $name = $this->getCacheKey($name);
107
+ $value = sqlite_escape_string(serialize($value));
108
+ if (is_null($expire)) {
109
+ $expire = $this->options['expire'];
110
+ }
111
+ if ($expire instanceof \DateTime) {
112
+ $expire = $expire->getTimestamp();
113
+ } else {
114
+ $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存
115
+ }
116
+ if (function_exists('gzcompress')) {
117
+ //数据压缩
118
+ $value = gzcompress($value, 3);
119
+ }
120
+ if ($this->tag) {
121
+ $tag = $this->tag;
122
+ $this->tag = null;
123
+ } else {
124
+ $tag = '';
125
+ }
126
+ $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')';
127
+ if (sqlite_query($this->handler, $sql)) {
128
+ return true;
129
+ }
130
+ return false;
131
+ }
132
+
133
+ /**
134
+ * 自增缓存(针对数值缓存)
135
+ * @access public
136
+ * @param string $name 缓存变量名
137
+ * @param int $step 步长
138
+ * @return false|int
139
+ */
140
+ public function inc($name, $step = 1)
141
+ {
142
+ if ($this->has($name)) {
143
+ $value = $this->get($name) + $step;
144
+ } else {
145
+ $value = $step;
146
+ }
147
+ return $this->set($name, $value, 0) ? $value : false;
148
+ }
149
+
150
+ /**
151
+ * 自减缓存(针对数值缓存)
152
+ * @access public
153
+ * @param string $name 缓存变量名
154
+ * @param int $step 步���
155
+ * @return false|int
156
+ */
157
+ public function dec($name, $step = 1)
158
+ {
159
+ if ($this->has($name)) {
160
+ $value = $this->get($name) - $step;
161
+ } else {
162
+ $value = -$step;
163
+ }
164
+ return $this->set($name, $value, 0) ? $value : false;
165
+ }
166
+
167
+ /**
168
+ * 删除缓存
169
+ * @access public
170
+ * @param string $name 缓存变量名
171
+ * @return boolean
172
+ */
173
+ public function rm($name)
174
+ {
175
+ $name = $this->getCacheKey($name);
176
+ $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\'';
177
+ sqlite_query($this->handler, $sql);
178
+ return true;
179
+ }
180
+
181
+ /**
182
+ * 清除缓存
183
+ * @access public
184
+ * @param string $tag 标签名
185
+ * @return boolean
186
+ */
187
+ public function clear($tag = null)
188
+ {
189
+ if ($tag) {
190
+ $name = sqlite_escape_string($tag);
191
+ $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\'';
192
+ sqlite_query($this->handler, $sql);
193
+ return true;
194
+ }
195
+ $sql = 'DELETE FROM ' . $this->options['table'];
196
+ sqlite_query($this->handler, $sql);
197
+ return true;
198
+ }
199
+ }
thinkphp/library/think/cache/driver/Wincache.php ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
12
+ namespace think\cache\driver;
13
+
14
+ use think\cache\Driver;
15
+
16
+ /**
17
+ * Wincache缓存驱动
18
+ * @author liu21st <liu21st@gmail.com>
19
+ */
20
+ class Wincache extends Driver
21
+ {
22
+ protected $options = [
23
+ 'prefix' => '',
24
+ 'expire' => 0,
25
+ ];
26
+
27
+ /**
28
+ * 构造函数
29
+ * @param array $options 缓存参数
30
+ * @throws \BadFunctionCallException
31
+ * @access public
32
+ */
33
+ public function __construct($options = [])
34
+ {
35
+ if (!function_exists('wincache_ucache_info')) {
36
+ throw new \BadFunctionCallException('not support: WinCache');
37
+ }
38
+ if (!empty($options)) {
39
+ $this->options = array_merge($this->options, $options);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * 判断缓存
45
+ * @access public
46
+ * @param string $name 缓存变量名
47
+ * @return bool
48
+ */
49
+ public function has($name)
50
+ {
51
+ $key = $this->getCacheKey($name);
52
+ return wincache_ucache_exists($key);
53
+ }
54
+
55
+ /**
56
+ * 读取缓存
57
+ * @access public
58
+ * @param string $name 缓存变量名
59
+ * @param mixed $default 默认值
60
+ * @return mixed
61
+ */
62
+ public function get($name, $default = false)
63
+ {
64
+ $key = $this->getCacheKey($name);
65
+ return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default;
66
+ }
67
+
68
+ /**
69
+ * 写入缓存
70
+ * @access public
71
+ * @param string $name 缓存变量名
72
+ * @param mixed $value 存储数据
73
+ * @param integer|\DateTime $expire 有效时间(秒)
74
+ * @return boolean
75
+ */
76
+ public function set($name, $value, $expire = null)
77
+ {
78
+ if (is_null($expire)) {
79
+ $expire = $this->options['expire'];
80
+ }
81
+ if ($expire instanceof \DateTime) {
82
+ $expire = $expire->getTimestamp() - time();
83
+ }
84
+ $key = $this->getCacheKey($name);
85
+ if ($this->tag && !$this->has($name)) {
86
+ $first = true;
87
+ }
88
+ if (wincache_ucache_set($key, $value, $expire)) {
89
+ isset($first) && $this->setTagItem($key);
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * 自增缓存(针对数值缓存)
97
+ * @access public
98
+ * @param string $name 缓存变量名
99
+ * @param int $step 步长
100
+ * @return false|int
101
+ */
102
+ public function inc($name, $step = 1)
103
+ {
104
+ $key = $this->getCacheKey($name);
105
+ return wincache_ucache_inc($key, $step);
106
+ }
107
+
108
+ /**
109
+ * 自减缓存(针对数值缓存)
110
+ * @access public
111
+ * @param string $name 缓存变量名
112
+ * @param int $step 步长
113
+ * @return false|int
114
+ */
115
+ public function dec($name, $step = 1)
116
+ {
117
+ $key = $this->getCacheKey($name);
118
+ return wincache_ucache_dec($key, $step);
119
+ }
120
+
121
+ /**
122
+ * 删除缓存
123
+ * @access public
124
+ * @param string $name 缓存变量名
125
+ * @return boolean
126
+ */
127
+ public function rm($name)
128
+ {
129
+ return wincache_ucache_delete($this->getCacheKey($name));
130
+ }
131
+
132
+ /**
133
+ * 清除缓存
134
+ * @access public
135
+ * @param string $tag 标签名
136
+ * @return boolean
137
+ */
138
+ public function clear($tag = null)
139
+ {
140
+ if ($tag) {
141
+ $keys = $this->getTagItem($tag);
142
+ foreach ($keys as $key) {
143
+ wincache_ucache_delete($key);
144
+ }
145
+ $this->rm('tag_' . md5($tag));
146
+ return true;
147
+ } else {
148
+ return wincache_ucache_clear();
149
+ }
150
+ }
151
+
152
+ }