diff --git a/Server/application/common/Server.rar b/Server/application/common/Server.rar new file mode 100644 index 00000000..d21f54d9 Binary files /dev/null and b/Server/application/common/Server.rar differ diff --git a/Server/application/common/Server/thinkphp/.gitignore b/Server/application/common/Server/thinkphp/.gitignore new file mode 100644 index 00000000..f7775ba4 --- /dev/null +++ b/Server/application/common/Server/thinkphp/.gitignore @@ -0,0 +1,8 @@ +/vendor +composer.phar +composer.lock +.DS_Store +Thumbs.db +/phpunit.xml +/.idea +/.vscode \ No newline at end of file diff --git a/Server/application/common/Server/thinkphp/.htaccess b/Server/application/common/Server/thinkphp/.htaccess new file mode 100644 index 00000000..3418e55a --- /dev/null +++ b/Server/application/common/Server/thinkphp/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/Server/application/common/Server/thinkphp/CONTRIBUTING.md b/Server/application/common/Server/thinkphp/CONTRIBUTING.md new file mode 100644 index 00000000..6cefcb38 --- /dev/null +++ b/Server/application/common/Server/thinkphp/CONTRIBUTING.md @@ -0,0 +1,119 @@ +如何贡献我的源代码 +=== + +此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。 + +## 通过 Github 贡献代码 + +ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 + +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 + +我们希望你贡献的代码符合: + +* ThinkPHP 的编码规范 +* 适当的注释,能让其他人读懂 +* 遵循 Apache2 开源协议 + +**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容** + +### 注意事项 + +* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141); +* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144); +* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。 +* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范; +* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests); + +## GitHub Issue + +GitHub 提供了 Issue 功能,该功能可以用于: + +* 提出 bug +* 提出功能改进 +* 反馈使用体验 + +该功能不应该用于: + + * 提出修改意见(涉及代码署名和修订追溯问题) + * 不友善的言论 + +## 快速修改 + +**GitHub 提供了快速编辑文件的功能** + +1. 登录 GitHub 帐号; +2. 浏览项目文件,找到要进行修改的文件; +3. 点击右上角铅笔图标进行修改; +4. 填写 `Commit changes` 相关内容(Title 必填); +5. 提交修改,等待 CI 验证和管理员合并。 + +**若您需要一次提交大量修改,请继续阅读下面的内容** + +## 完整流程 + +1. `fork`本项目; +2. 克隆(`clone`)你 `fork` 的项目到本地; +3. 新建分支(`branch`)并检出(`checkout`)新分支; +4. 添加本项目到你的本地 git 仓库作为上游(`upstream`); +5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests); +6. 变基(衍合 `rebase`)你的分支到上游 master 分支; +7. `push` 你的本地仓库到 GitHub; +8. 提交 `pull request`; +9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`); +10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 + +*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* + +*绝对不可以使用 `git push -f` 强行推送修改到上游* + +### 注意事项 + +* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/); +* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分); +* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/) + +## 推荐资源 + +### 开发环境 + +* XAMPP for Windows 5.5.x +* WampServer (for Windows) +* upupw Apache PHP5.4 ( for Windows) + +或自行安装 + +- Apache / Nginx +- PHP 5.4 ~ 5.6 +- MySQL / MariaDB + +*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer* + +*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB* + +### 编辑器 + +Sublime Text 3 + phpfmt 插件 + +phpfmt 插件参数 + +```json +{ + "autocomplete": true, + "enable_auto_align": true, + "format_on_save": true, + "indent_with_space": true, + "psr1_naming": false, + "psr2": true, + "version": 4 +} +``` + +或其他 编辑器 / IDE 配合 PSR2 自动格式化工具 + +### Git GUI + +* SourceTree +* GitHub Desktop + +或其他 Git 图形界面客户端 diff --git a/Server/application/common/Server/thinkphp/LICENSE.txt b/Server/application/common/Server/thinkphp/LICENSE.txt new file mode 100644 index 00000000..774fa76f --- /dev/null +++ b/Server/application/common/Server/thinkphp/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Server/application/common/Server/thinkphp/README.md b/Server/application/common/Server/thinkphp/README.md new file mode 100644 index 00000000..1339e6c7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/README.md @@ -0,0 +1,99 @@ +![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) + +ThinkPHP 5.1(LTS) —— 12载初心,你值得信赖的PHP框架 +=============== + +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=5.1)](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1) +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) +[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) +[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) +[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-8892BF.svg)](http://www.php.net/) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: + + + 采用容器统一管理对象 + + 支持Facade + + 更易用的路由 + + 注解路由支持 + + 路由跨域请求支持 + + 验证类增强 + + 配置和路由目录独立 + + 取消系统常量 + + 类库别名机制 + + 模型和数据库增强 + + 依赖注入完善 + + 支持PSR-3日志规范 + + 中间件支持(`V5.1.6+`) + + 支持`Swoole`/`Workerman`运行(`V5.1.18+`) + +官方已经正式宣布`5.1.27`版本为LTS版本。 + +### 废除的功能: + + + 聚合模型 + + 内置控制器扩展类 + + 模型自动验证 + +> ThinkPHP5.1的运行环境要求PHP5.6+ 兼容PHP8.0。 + + +## 安装 + +使用composer安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +更新框架 +~~~ +composer update topthink/framework +~~~ + + +## 在线手册 + ++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content) ++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) + + +## 官方服务 + ++ [应用服务市场](https://market.topthink.com/) ++ [ThinkAPI——统一API服务](https://docs.topthink.com/think-api) + +## 命名规范 + +`ThinkPHP5.1`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/Server/application/common/Server/thinkphp/base.php b/Server/application/common/Server/thinkphp/base.php new file mode 100644 index 00000000..d7238cc6 --- /dev/null +++ b/Server/application/common/Server/thinkphp/base.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +namespace think; + +// 载入Loader类 +require __DIR__ . '/library/think/Loader.php'; + +// 注册自动加载 +Loader::register(); + +// 注册错误和异常处理机制 +Error::register(); + +// 实现日志接口 +if (interface_exists('Psr\Log\LoggerInterface')) { + interface LoggerInterface extends \Psr\Log\LoggerInterface + {} +} else { + interface LoggerInterface + {} +} + +// 注册类库别名 +Loader::addClassAlias([ + 'App' => facade\App::class, + 'Build' => facade\Build::class, + 'Cache' => facade\Cache::class, + 'Config' => facade\Config::class, + 'Cookie' => facade\Cookie::class, + 'Db' => Db::class, + 'Debug' => facade\Debug::class, + 'Env' => facade\Env::class, + 'Facade' => Facade::class, + 'Hook' => facade\Hook::class, + 'Lang' => facade\Lang::class, + 'Log' => facade\Log::class, + 'Request' => facade\Request::class, + 'Response' => facade\Response::class, + 'Route' => facade\Route::class, + 'Session' => facade\Session::class, + 'Url' => facade\Url::class, + 'Validate' => facade\Validate::class, + 'View' => facade\View::class, +]); diff --git a/Server/application/common/Server/thinkphp/composer.json b/Server/application/common/Server/thinkphp/composer.json new file mode 100644 index 00000000..33477b1d --- /dev/null +++ b/Server/application/common/Server/thinkphp/composer.json @@ -0,0 +1,35 @@ +{ + "name": "topthink/framework", + "description": "the new thinkphp framework", + "type": "think-framework", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=5.6.0", + "topthink/think-installer": "2.*" + }, + "require-dev": { + "phpunit/phpunit": "^5.0|^6.0", + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phploc/phploc": "2.*", + "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "2.*", + "phpdocumentor/reflection-docblock": "^2.0" + } +} diff --git a/Server/application/common/Server/thinkphp/convention.php b/Server/application/common/Server/thinkphp/convention.php new file mode 100644 index 00000000..1d85e56e --- /dev/null +++ b/Server/application/common/Server/thinkphp/convention.php @@ -0,0 +1,327 @@ + [ + // 应用名称 + 'app_name' => '', + // 应用地址 + 'app_host' => '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否HTTPS + 'is_https' => false, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认验证器 + 'default_validate' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 自动搜索控制器 + 'controller_auto_search' => false, + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 默认的空模块名 + 'empty_module' => '', + // 默认模块名 + 'default_module' => 'index', + // 是否支持多模块 + 'app_multi_module' => true, + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | URL请求设置 + // +---------------------------------------------------------------------- + + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'HTTP_X_REAL_IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 路由设置 + // +---------------------------------------------------------------------- + + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 使用注解路由 + 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '\w+', + // 是否开启路由缓存 + 'route_check_cache' => false, + // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5 + 'route_check_cache_key' => '', + // 路由缓存的设置 + 'route_cache_option' => [], + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + // 异常页面的模板文件 + 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl', + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + ], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + //'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + // 是否记录trace信息到日志 + 'record_trace' => false, + // 是否JSON格式记录 + 'json' => false, + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + 'file' => __DIR__ . '/tpl/page_trace.tpl', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + //'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => 'think', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + 'httponly' => true, + 'secure' => false, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + // +---------------------------------------------------------------------- + // | 数据库设置 + // +---------------------------------------------------------------------- + + 'database' => [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // 查询对象 + 'query' => '\\think\\db\\Query', + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], + + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + 'auto_path' => '', + ], + + // 中间件配置 + 'middleware' => [ + 'default_namespace' => 'app\\http\\middleware\\', + ], +]; diff --git a/Server/application/common/Server/thinkphp/helper.php b/Server/application/common/Server/thinkphp/helper.php new file mode 100644 index 00000000..72b9e9fd --- /dev/null +++ b/Server/application/common/Server/thinkphp/helper.php @@ -0,0 +1,726 @@ + +// +---------------------------------------------------------------------- + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\Container; +use think\Db; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\facade\Cache; +use think\facade\Config; +use think\facade\Cookie; +use think\facade\Debug; +use think\facade\Env; +use think\facade\Hook; +use think\facade\Lang; +use think\facade\Log; +use think\facade\Request; +use think\facade\Route; +use think\facade\Session; +use think\facade\Url; +use think\Response; +use think\route\RuleItem; + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, $message = null, $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + return app()->action($url, $vars, $layer, $appendSuffix); + } +} + +if (!function_exists('app')) { + /** + * 快速获取容器中的实例 支持依赖注入 + * @param string $name 类名或标识 默认获取当前应用实例 + * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed|\think\App + */ + function app($name = 'think\App', $args = [], $newInstance = false) + { + return Container::get($name, $args, $newInstance); + } +} + +if (!function_exists('behavior')) { + /** + * 执行某个行为(run方法) 支持依赖注入 + * @param mixed $behavior 行为类名或者别名 + * @param mixed $args 参数 + * @return mixed + */ + function behavior($behavior, $args = null) + { + return Hook::exec($behavior, $args); + } +} + +if (!function_exists('bind')) { + /** + * 绑定一个类到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + function bind($abstract, $concrete = null) + { + return Container::getInstance()->bindTo($abstract, $concrete); + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); + } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::rm($name); + } + + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } +} + +if (!function_exists('call')) { + /** + * 调用反射执行callable 支持依赖注入 + * @param mixed $callable 支持闭包等callable写法 + * @param array $args 参数 + * @return mixed + */ + function call($callable, $args = []) + { + return Container::getInstance()->invoke($callable, $args); + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param $class + * @return array + */ + function class_uses_recursive($class) + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + function config($name = '', $value = null) + { + if (is_null($value) && is_string($name)) { + if ('.' == substr($name, -1)) { + return Config::pull(substr($name, 0, -1)); + } + + return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name); + } else { + return Config::set($name, $value); + } + } +} + +if (!function_exists('container')) { + /** + * 获取容器对象实例 + * @return Container + */ + function container() + { + return Container::getInstance(); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return app()->controller($name, $layer, $appendSuffix); + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string|array $name cookie名称,如果为数组表示进行cookie设置 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie($name, $value = '', $option = null) + { + if (is_array($name)) { + // 初始化 + Cookie::init($name); + } elseif (is_null($name)) { + // 清除 + Cookie::clear($value); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); + } elseif (is_null($value)) { + // 删除 + return Cookie::delete($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('db')) { + /** + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = true) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 + * @return mixed + */ + function debug($start, $end = '', $dec = 6) + { + if ('' == $end) { + Debug::remark($start); + } else { + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('download')) { + /** + * 获取\think\response\Download对象实例 + * @param string $filename 要下载的文件 + * @param string $name 显示文件名 + * @param bool $content 是否为内容 + * @param integer $expire 有效期(秒) + * @return \think\response\Download + */ + function download($filename, $name = '', $content = false, $expire = 360, $openinBrowser = false) + { + return Response::create($filename, 'download')->name($name)->isContent($content)->expire($expire)->openinBrowser($openinBrowser); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env($name = null, $default = null) + { + return Env::get($name, $default); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); + } else { + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'json', $code, $header, $options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'jsonp', $code, $header, $options); + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return app()->model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + return Response::create($url, 'redirect', $code)->params($params); + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return app('request'); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = '', $code = 200, $header = [], $type = 'html') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('route')) { + /** + * 路由注册 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + function route($rule, $route, $option = [], $pattern = []) + { + return Route::rule($rule, $route, '*', $option, $pattern); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear($value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::token($name, $type); + + return ''; + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return array|void + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait + * @return array + */ + function trait_uses_recursive($trait) + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return app()->validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param integer $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function view($template = '', $vars = [], $code = 200, $filter = null) + { + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + $result = app()->action($name, $data, 'widget'); + + if (is_object($result)) { + $result = $result->getContent(); + } + + return $result; + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'xml', $code, $header, $options); + } +} + +if (!function_exists('yaconf')) { + /** + * 获取yaconf配置 + * + * @param string $name 配置参数名 + * @param mixed $default 默认值 + * @return mixed + */ + function yaconf($name, $default = null) + { + return Config::yaconf($name, $default); + } +} diff --git a/Server/application/common/Server/thinkphp/lang/zh-cn.php b/Server/application/common/Server/thinkphp/lang/zh-cn.php new file mode 100644 index 00000000..1e050820 --- /dev/null +++ b/Server/application/common/Server/thinkphp/lang/zh-cn.php @@ -0,0 +1,144 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 系统错误提示 + 'Undefined variable' => '未定义变量', + 'Undefined index' => '未定义数组索引', + 'Undefined offset' => '未定义数组下标', + 'Parse error' => '语法解析错误', + 'Type error' => '类型错误', + 'Fatal error' => '致命错误', + 'syntax error' => '语法错误', + + // 框架核心错误提示 + 'dispatch type not support' => '不支持的调度类型', + 'method param miss' => '方法参数错误', + 'method not exists' => '方法不存在', + 'function not exists' => '函数不存在', + 'file not exists' => '文件不存在', + 'module not exists' => '模块不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Route Not Found' => '当前访问路由未定义或不匹配', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + 'redisd master' => 'Redisd 主服务器错误', + 'redisd slave' => 'Redisd 从服务器错误', + 'must run at sae' => '必须在SAE运行', + 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务', + 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', + 'fields not exists' => '数据表字段不存在', + 'where express error' => '查询表达式错误', + 'order express error' => '排序表达式错误', + 'no data to update' => '没有任何数据需要更新', + 'miss data to insert' => '缺少需要写入的数据', + 'not support data' => '不支持的数据表达式', + 'miss complex primary data' => '缺少复合主键数据', + 'miss update condition' => '缺少更新条件', + 'model data Not Found' => '模型数据不存在', + 'table data not Found' => '表数据不存在', + 'delete without condition' => '没有条件不会执行删除操作', + 'miss relation data' => '缺少关联表数据', + 'tag attr must' => '模板标签属性必须', + 'tag error' => '模板标签错误', + 'cache write error' => '缓存写入失败', + 'sae mc write error' => 'SAE mc 写入错误', + 'route name not exists' => '路由标识不存在(或参数不够)', + 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', + 'chunk not support order' => 'Chunk不支持调用order方法', + 'route pattern error' => '路由变量规则定义错误', + 'route behavior will not support' => '路由行为废弃(使用中间件替代)', + 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', + + // 上传错误信息 + 'unknown upload error' => '未知上传错误!', + 'file write error' => '文件写入失败!', + 'upload temp dir not found' => '找不到临时文件夹!', + 'no file to uploaded' => '没有文件被上传!', + 'only the portion of file is uploaded' => '文件只有部分被上传!', + 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', + 'upload write error' => '文件上传保存错误!', + 'has the same filename: {:filename}' => '存在同名文件:{:filename}', + 'upload illegal files' => '非法上传文件', + 'illegal image files' => '非法图片文件', + 'extensions to upload is not allowed' => '上传文件后缀不允许', + 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!', + 'filesize not match' => '上传文件大小不符!', + 'directory {:path} creation failed' => '目录 {:path} 创建失败!', + + 'The middleware must return Response instance' => '中间件方法必须返回Response对象实例', + 'The queue was exhausted, with no response returned' => '中间件队列为空', + // Validate Error Message + ':attribute require' => ':attribute不能为空', + ':attribute must' => ':attribute必须', + ':attribute must be numeric' => ':attribute必须是数字', + ':attribute must be integer' => ':attribute必须是整数', + ':attribute must be float' => ':attribute必须是浮点数', + ':attribute must be bool' => ':attribute必须是布尔值', + ':attribute not a valid email address' => ':attribute格式不符', + ':attribute not a valid mobile' => ':attribute格式不符', + ':attribute must be a array' => ':attribute必须是数组', + ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1', + ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式', + ':attribute not a valid file' => ':attribute不是有效的上传文件', + ':attribute not a valid image' => ':attribute不是有效的图像文件', + ':attribute must be alpha' => ':attribute只能是字母', + ':attribute must be alpha-numeric' => ':attribute只能是字母和数字', + ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-', + ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP', + ':attribute must be chinese' => ':attribute只能是汉字', + ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母', + ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字', + ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-', + ':attribute not a valid url' => ':attribute不是有效的URL地址', + ':attribute not a valid ip' => ':attribute不是有效的IP地址', + ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule', + ':attribute must be in :rule' => ':attribute必须在 :rule 范围内', + ':attribute be notin :rule' => ':attribute不能在 :rule 范围内', + ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间', + ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间', + 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule', + 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule', + 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule', + ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule', + ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule', + ':attribute not within :rule' => '不在有效期内 :rule', + 'access IP is not allowed' => '不允许的IP访问', + 'access IP denied' => '禁止的IP访问', + ':attribute out of accord with :2' => ':attribute和确认字段:2不一致', + ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同', + ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule', + ':attribute must greater than :rule' => ':attribute必须大于 :rule', + ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule', + ':attribute must less than :rule' => ':attribute必须小于 :rule', + ':attribute must equal :rule' => ':attribute必须等于 :rule', + ':attribute has exists' => ':attribute已存在', + ':attribute not conform to the rules' => ':attribute不符合指定规则', + 'invalid Request method' => '无效的请求类型', + 'invalid token' => '令牌数据无效', + 'not conform to the rules' => '规则错误', +]; diff --git a/Server/application/common/Server/thinkphp/library/think/App.php b/Server/application/common/Server/thinkphp/library/think/App.php new file mode 100644 index 00000000..692b4227 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/App.php @@ -0,0 +1,979 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpResponseException; +use think\route\Dispatch; + +/** + * App 应用管理 + */ +class App extends Container +{ + const VERSION = '5.1.41 LTS'; + + /** + * 当前模块路径 + * @var string + */ + protected $modulePath; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = true; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用类库后缀 + * @var bool + */ + protected $suffix = false; + + /** + * 严格路由检测 + * @var bool + */ + protected $routeMust; + + /** + * 应用类库目录 + * @var string + */ + protected $appPath; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath; + + /** + * 运行时目录 + * @var string + */ + protected $runtimePath; + + /** + * 配置目录 + * @var string + */ + protected $configPath; + + /** + * 路由目录 + * @var string + */ + protected $routePath; + + /** + * 配置后缀 + * @var string + */ + protected $configExt; + + /** + * 应用调度实例 + * @var Dispatch + */ + protected $dispatch; + + /** + * 绑定模块(控制器) + * @var string + */ + protected $bindModule; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + public function __construct($appPath = '') + { + $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $this->path($appPath); + } + + /** + * 绑定模块或者控制器 + * @access public + * @param string $bind + * @return $this + */ + public function bind($bind) + { + $this->bindModule = $bind; + return $this; + } + + /** + * 设置应用类库目录 + * @access public + * @param string $path 路径 + * @return $this + */ + public function path($path) + { + $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath(); + + return $this; + } + + /** + * 初始化应用 + * @access public + * @return void + */ + public function initialize() + { + if ($this->initialized) { + return; + } + + $this->initialized = true; + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; + $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + + static::setInstance($this); + + $this->instance('app', $this); + + // 加载环境变量配置文件 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + + $this->configExt = $this->env->get('config_ext', '.php'); + + // 加载惯例配置文件 + $this->config->set(include $this->thinkPath . 'convention.php'); + + // 设置路径环境变量 + $this->env->set([ + 'think_path' => $this->thinkPath, + 'root_path' => $this->rootPath, + 'app_path' => $this->appPath, + 'config_path' => $this->configPath, + 'route_path' => $this->routePath, + 'runtime_path' => $this->runtimePath, + 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, + 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, + ]); + + $this->namespace = $this->env->get('app_namespace', $this->namespace); + $this->env->set('app_namespace', $this->namespace); + + // 注册应用命名空间 + Loader::addNamespace($this->namespace, $this->appPath); + + // 初始化应用 + $this->init(); + + // 开启类名后缀 + $this->suffix = $this->config('app.class_suffix'); + + // 应用调试模式 + $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->appDebug); + + if (!$this->appDebug) { + ini_set('display_errors', 'Off'); + } elseif (PHP_SAPI != 'cli') { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + + // 注册异常处理类 + if ($this->config('app.exception_handle')) { + Error::setExceptionHandler($this->config('app.exception_handle')); + } + + // 注册根命名空间 + if (!empty($this->config('app.root_namespace'))) { + Loader::addNamespace($this->config('app.root_namespace')); + } + + // 加载composer autofile文件 + Loader::loadComposerAutoloadFiles(); + + // 注册类库别名 + Loader::addClassAlias($this->config->pull('alias')); + + // 数据库配置初始化 + Db::init($this->config->pull('database')); + + // 设置系统时区 + date_default_timezone_set($this->config('app.default_timezone')); + + // 读取语言包 + $this->loadLangPack(); + + // 路由初始化 + $this->routeInit(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return void + */ + public function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DIRECTORY_SEPARATOR : ''; + $path = $this->appPath . $module; + + // 加载初始化文件 + if (is_file($path . 'init.php')) { + include $path . 'init.php'; + } elseif (is_file($this->runtimePath . $module . 'init.php')) { + include $this->runtimePath . $module . 'init.php'; + } else { + // 加载行为扩展文件 + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $this->hook->import($tags); + } + } + + // 加载公共文件 + if (is_file($path . 'common.php')) { + include_once $path . 'common.php'; + } + + if ('' == $module) { + // 加载系统助手函数 + include $this->thinkPath . 'helper.php'; + } + + // 加载中间件 + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $this->middleware->import($middleware); + } + } + + // 注册服务的容器对象实例 + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $this->bindTo($provider); + } + } + + // 自动读取配置文件 + if (is_dir($path . 'config')) { + $dir = $path . 'config' . DIRECTORY_SEPARATOR; + } elseif (is_dir($this->configPath . $module)) { + $dir = $this->configPath . $module; + } + + $files = isset($dir) ? scandir($dir) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { + $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + $this->setModulePath($path); + + if ($module) { + // 对容器中的对象实例进行配置更新 + $this->containerConfigUpdate($module); + } + } + + protected function containerConfigUpdate($module) + { + $config = $this->config->get(); + + // 注册异常处理类 + if ($config['app']['exception_handle']) { + Error::setExceptionHandler($config['app']['exception_handle']); + } + + Db::init($config['database']); + $this->middleware->setConfig($config['middleware']); + $this->route->setConfig($config['app']); + $this->request->init($config['app']); + $this->cookie->init($config['cookie']); + $this->view->init($config['template']); + $this->log->init($config['log']); + $this->session->setConfig($config['session']); + $this->debug->setConfig($config['trace']); + $this->cache->init($config['cache'], true); + + // 加载当前模块语言包 + $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); + + // 模块请求缓存检查 + $this->checkRequestCache( + $config['app']['request_cache'], + $config['app']['request_cache_expire'], + $config['app']['request_cache_except'] + ); + } + + /** + * 执行应用程序 + * @access public + * @return Response + * @throws Exception + */ + public function run() + { + try { + // 初始化应用 + $this->initialize(); + + // 监听app_init + $this->hook->listen('app_init'); + + if ($this->bindModule) { + // 模块/控制器绑定 + $this->route->bind($this->bindModule); + } elseif ($this->config('app.auto_bind_module')) { + // 入口自动绑定 + $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir($this->appPath . $name)) { + $this->route->bind($name); + } + } + + // 监听app_dispatch + $this->hook->listen('app_dispatch'); + + $dispatch = $this->dispatch; + + if (empty($dispatch)) { + // 路由检测 + $dispatch = $this->routeCheck()->init(); + } + + // 记录当前调度信息 + $this->request->dispatch($dispatch); + + // 记录路由和请求信息 + if ($this->appDebug) { + $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); + $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); + $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); + } + + // 监听app_begin + $this->hook->listen('app_begin'); + + // 请求缓存检查 + $this->checkRequestCache( + $this->config('request_cache'), + $this->config('request_cache_expire'), + $this->config('request_cache_except') + ); + + $data = null; + } catch (HttpResponseException $exception) { + $dispatch = null; + $data = $exception->getResponse(); + } + + $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { + return is_null($data) ? $dispatch->run() : $data; + }); + + $response = $this->middleware->dispatch($this->request); + + // 监听app_end + $this->hook->listen('app_end', $response); + + return $response; + } + + protected function getRouteCacheKey() + { + if ($this->config->get('route_check_cache_key')) { + $closure = $this->config->get('route_check_cache_key'); + $routeKey = $closure($this->request); + } else { + $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); + } + + return $routeKey; + } + + protected function loadLangPack() + { + // 读取默认语言 + $this->lang->range($this->config('app.default_lang')); + + if ($this->config('app.lang_switch_on')) { + // 开启多语言机制 检测当前语言 + $this->lang->detect(); + } + + $this->request->setLangset($this->lang->range()); + + // 加载系统语言包 + $this->lang->load([ + $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + ]); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function checkRequestCache($key, $expire = null, $except = [], $tag = null) + { + $cache = $this->request->cache($key, $expire, $except, $tag); + + if ($cache) { + $this->setResponseCache($cache); + } + } + + public function setResponseCache($cache) + { + list($key, $expire, $tag) = $cache; + + if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { + // 读取缓存 + $response = Response::create()->code(304); + throw new HttpResponseException($response); + } elseif ($this->cache->has($key)) { + list($content, $header) = $this->cache->get($key); + + $response = Response::create($content)->header($header); + throw new HttpResponseException($response); + } + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param Dispatch $dispatch 调度信息 + * @return $this + */ + public function dispatch(Dispatch $dispatch) + { + $this->dispatch = $dispatch; + return $this; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public function log($msg, $type = 'info') + { + $this->appDebug && $this->log->record($msg, $type); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 .号分割) + * @return mixed + */ + public function config($name = '') + { + return $this->config->get($name); + } + + /** + * 路由初始化 导入路由定义规则 + * @access public + * @return void + */ + public function routeInit() + { + // 路由检测 + if (is_dir($this->routePath)) { + $files = glob($this->routePath . '*.php'); + foreach ($files as $file) { + $rules = include $file; + if (is_array($rules)) { + $this->route->import($rules); + } + } + } + + if ($this->route->config('route_annotation')) { + // 自动生成路由定义 + if ($this->appDebug) { + $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); + $this->build->buildRoute($suffix); + } + + $filename = $this->runtimePath . 'build_route.php'; + + if (is_file($filename)) { + include $filename; + } + } + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @return Dispatch + */ + public function routeCheck() + { + // 检测路由缓存 + if (!$this->appDebug && $this->config->get('route_check_cache')) { + $routeKey = $this->getRouteCacheKey(); + $option = $this->config->get('route_cache_option'); + + if ($option && $this->cache->connect($option)->has($routeKey)) { + return $this->cache->connect($option)->get($routeKey); + } elseif ($this->cache->has($routeKey)) { + return $this->cache->get($routeKey); + } + } + + // 获取应用调度信息 + $path = $this->request->path(); + + // 是否强制路由模式 + $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); + + // 路由检测 返回一个Dispatch对象 + $dispatch = $this->route->check($path, $must); + + if (!empty($routeKey)) { + try { + if ($option) { + $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch); + } else { + $this->cache->tag('route_cache')->set($routeKey, $dispatch); + } + } catch (\Exception $e) { + // 存在闭包的时候缓存无效 + } + } + + return $dispatch; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $must 是否强制检测路由 + * @return $this + */ + public function routeMust($must = false) + { + $this->routeMust = $must; + return $this; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected function parseModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $class = $name; + $module = $this->request->module(); + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = $this->request->module(); + } + + $class = $this->parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 实例化应用类库 + * @access public + * @param string $name 类名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public function create($name, $layer, $appendSuffix = false, $common = 'common') + { + $guid = $name . $layer; + + if ($this->__isset($guid)) { + return $this->__get($guid); + } + + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $object = $this->__get($class); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + if (class_exists($class)) { + $object = $this->__get($class); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + $this->__set($guid, $class); + + return $object; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Model + * @throws ClassNotFoundException + */ + public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return $this->make($class, true); + } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { + return $this->make($emptyClass, true); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Validate + * @throws ClassNotFoundException + */ + public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: $this->config('default_validate'); + + if (empty($name)) { + return new Validate; + } + + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 数据库初始化 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Query + */ + public function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + * @throws ClassNotFoundException + */ + public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); + $class = $this->controller($module, $layer, $appendSuffix); + + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix + * @return string + */ + public function parseClass($module, $layer, $name, $appendSuffix = false) + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version() + { + return static::VERSION; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug() + { + return $this->appDebug; + } + + /** + * 获取模块路径 + * @access public + * @return string + */ + public function getModulePath() + { + return $this->modulePath; + } + + /** + * 设置模块路径 + * @access public + * @param string $path 路径 + * @return void + */ + public function setModulePath($path) + { + $this->modulePath = $path; + $this->env->set('module_path', $path); + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath() + { + return $this->rootPath; + } + + /** + * 获取应用类库目录 + * @access public + * @return string + */ + public function getAppPath() + { + if (is_null($this->appPath)) { + $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; + } + + return $this->appPath; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath() + { + return $this->runtimePath; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath() + { + return $this->thinkPath; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath() + { + return $this->routePath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath() + { + return $this->configPath; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt() + { + return $this->configExt; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * 设置应用类库命名空间 + * @access public + * @param string $namespace 命名空间名称 + * @return $this + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 是否启用类库后缀 + * @access public + * @return bool + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime() + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem() + { + return $this->beginMem; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/Build.php b/Server/application/common/Server/thinkphp/library/think/Build.php new file mode 100644 index 00000000..7a531d74 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Build.php @@ -0,0 +1,415 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Build +{ + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 应用目录 + * @var string + */ + protected $basePath; + + public function __construct(App $app) + { + $this->app = $app; + $this->basePath = $this->app->getAppPath(); + } + + /** + * 根据传入的build资料创建目录和文件 + * @access public + * @param array $build build列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + */ + public function run(array $build = [], $namespace = 'app', $suffix = false) + { + // 锁定 + $lockfile = $this->basePath . 'build.lock'; + + if (is_writable($lockfile)) { + return; + } elseif (!touch($lockfile)) { + throw new Exception('应用目录[' . $this->basePath . ']不可写,目录无法自动生成!
请手动生成项目目录~', 10006); + } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + $this->buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + $this->buildFile($list); + } else { + // 创建模块 + $this->module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lockfile); + } + + /** + * 创建目录 + * @access protected + * @param array $list 目录列表 + * @return void + */ + protected function buildDir($list) + { + foreach ($list as $dir) { + $this->checkDirBuild($this->basePath . $dir); + } + } + + /** + * 创建文件 + * @access protected + * @param array $list 文件列表 + * @return void + */ + protected function buildFile($list) + { + foreach ($list as $file) { + if (!is_dir($this->basePath . dirname($file))) { + // 创建目录 + mkdir($this->basePath . dirname($file), 0755, true); + } + + if (!is_file($this->basePath . $file)) { + file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "basePath . $module)) { + // 创建模块目录 + mkdir($this->basePath . $module); + } + + if (basename($this->app->getRuntimePath()) != $module) { + // 创建配置文件和公共文件 + $this->buildCommon($module); + // 创建模块的默认页面 + $this->buildHello($module, $namespace, $suffix); + } + + if (empty($list)) { + // 创建默认的模块目录和文件 + $list = [ + '__file__' => ['common.php'], + '__dir__' => ['controller', 'model', 'view', 'config'], + ]; + } + + // 创建子目录和文件 + foreach ($list as $path => $file) { + $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR; + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + $this->checkDirBuild($modulePath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($modulePath . $name)) { + file_put_contents($modulePath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "checkDirBuild(dirname($filename)); + $content = ''; + break; + default: + // 其他文件 + $content = "app->getNameSpace(); + $content = 'app->config('app.url_controller_layer'); + } + + if ($this->app->config('app.app_multi_module')) { + $modules = glob($this->basePath . '*', GLOB_ONLYDIR); + + foreach ($modules as $module) { + $module = basename($module); + + if (in_array($module, $this->app->config('app.deny_module_list'))) { + continue; + } + + $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR; + $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer); + } + } else { + $path = $this->basePath . $layer . DIRECTORY_SEPARATOR; + $content .= $this->buildDirRoute($path, $namespace, '', $suffix, $layer); + } + + $filename = $this->app->getRuntimePath() . 'build_route.php'; + file_put_contents($filename, $content); + + return $filename; + } + + /** + * 生成子目录控制器类的路由规则 + * @access protected + * @param string $path 控制器目录 + * @param string $namespace 应用命名空间 + * @param string $module 模块 + * @param bool $suffix 类库后缀 + * @param string $layer 控制器层目录名 + * @return string + */ + protected function buildDirRoute($path, $namespace, $module, $suffix, $layer) + { + $content = ''; + $controllers = glob($path . '*.php'); + + foreach ($controllers as $controller) { + $controller = basename($controller, '.php'); + + $class = new \ReflectionClass($namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $controller); + + if (strpos($layer, '\\')) { + // 多级控制器 + $level = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11)); + $controller = $level . '.' . $controller; + $length = strlen(strstr($layer, '\\', true)); + } else { + $length = strlen($layer); + } + + if ($suffix) { + $controller = substr($controller, 0, -$length); + } + + $content .= $this->getControllerRoute($class, $module, $controller); + } + + $subDir = glob($path . '*', GLOB_ONLYDIR); + + foreach ($subDir as $dir) { + $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir)); + } + + return $content; + } + + /** + * 生成控制器类的路由规则 + * @access protected + * @param string $class 控制器完整类名 + * @param string $module 模块名 + * @param string $controller 控制器名 + * @return string + */ + protected function getControllerRoute($class, $module, $controller) + { + $content = ''; + $comment = $class->getDocComment(); + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $route = ($module ? $module . '/' : '') . $controller; + $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } elseif (false !== strpos($comment, '@alias(')) { + $comment = $this->parseRouteComment($comment, '@alias('); + $route = ($module ? $module . '/' : '') . $controller; + $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } + + $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); + + foreach ($methods as $method) { + $comment = $this->getMethodRouteComment($module, $controller, $method); + if ($comment) { + $content .= PHP_EOL . $comment; + } + } + + return $content; + } + + /** + * 解析路由注释 + * @access protected + * @param string $comment + * @param string $tag + * @return string + */ + protected function parseRouteComment($comment, $tag = '@route(') + { + $comment = substr($comment, 3, -2); + $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1)); + $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment); + + if (count($comment) > 1) { + $key = array_search('', $comment); + $comment = array_slice($comment, 0, false === $key ? 1 : $key); + } + + $comment = implode(PHP_EOL . "\t", $comment) . ';'; + + if (strpos($comment, '{')) { + $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) { + return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0]; + }, $comment); + } + return $comment; + } + + /** + * 获取方法的路由注释 + * @access protected + * @param string $module 模块 + * @param string $controller 控制器名 + * @param \ReflectMethod $reflectMethod + * @return string|void + */ + protected function getMethodRouteComment($module, $controller, $reflectMethod) + { + $comment = $reflectMethod->getDocComment(); + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $action = $reflectMethod->getName(); + + if ($suffix = $this->app->config('app.action_suffix')) { + $action = substr($action, 0, -strlen($suffix)); + } + + $route = ($module ? $module . '/' : '') . $controller . '/' . $action; + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment); + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment); + + return $comment; + } + } + + /** + * 创建模块的欢迎页面 + * @access protected + * @param string $module 模块名 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + */ + protected function buildHello($module, $namespace, $suffix = false) + { + $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . ($suffix ? 'Controller' : '') . '.php'; + if (!is_file($filename)) { + $content = file_get_contents($this->app->getThinkPath() . 'tpl' . DIRECTORY_SEPARATOR . 'default_index.tpl'); + $content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content); + $this->checkDirBuild(dirname($filename)); + + file_put_contents($filename, $content); + } + } + + /** + * 创建模块的公共文件 + * @access protected + * @param string $module 模块名 + * @return void + */ + protected function buildCommon($module) + { + $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php'; + $this->checkDirBuild(dirname($filename)); + + if (!is_file($filename)) { + file_put_contents($filename, "basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php'; + + if (!is_file($filename)) { + file_put_contents($filename, " +// +---------------------------------------------------------------------- + +namespace think; + +use think\cache\Driver; + +/** + * Class Cache + * + * @package think + * + * @mixin Driver + * @mixin \think\cache\driver\File + */ +class Cache +{ + /** + * 缓存实例 + * @var array + */ + protected $instance = []; + + /** + * 缓存配置 + * @var array + */ + protected $config = []; + + /** + * 操作句柄 + * @var object + */ + protected $handler; + + public function __construct(array $config = []) + { + $this->config = $config; + $this->init($config); + } + + /** + * 连接缓存 + * @access public + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @return Driver + */ + public function connect(array $options = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($options)); + } + + if (true === $name || !isset($this->instance[$name])) { + $type = !empty($options['type']) ? $options['type'] : 'File'; + + if (true === $name) { + $name = md5(serialize($options)); + } + + $this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options); + } + + return $this->instance[$name]; + } + + /** + * 自动初始化缓存 + * @access public + * @param array $options 配置数组 + * @param bool $force 强制更新 + * @return Driver + */ + public function init(array $options = [], $force = false) + { + if (is_null($this->handler) || $force) { + + if ('complex' == $options['type']) { + $default = $options['default']; + $options = isset($options[$default['type']]) ? $options[$default['type']] : $default; + } + + $this->handler = $this->connect($options); + } + + return $this->handler; + } + + public static function __make(Config $config) + { + return new static($config->pull('cache')); + } + + public function getConfig() + { + return $this->config; + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 切换缓存类型 需要配置 cache.type 为 complex + * @access public + * @param string $name 缓存标识 + * @return Driver + */ + public function store($name = '') + { + if ('' !== $name && 'complex' == $this->config['type']) { + return $this->connect($this->config[$name], strtolower($name)); + } + + return $this->init(); + } + + public function __call($method, $args) + { + return call_user_func_array([$this->init(), $method], $args); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/Collection.php b/Server/application/common/Server/thinkphp/library/think/Collection.php new file mode 100644 index 00000000..d7454ec5 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Collection.php @@ -0,0 +1,552 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 数据集数据 + * @var array + */ + protected $items = []; + + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + public static function make($items = []) + { + return new static($items); + } + + /** + * 是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + public function toArray() + { + return array_map(function ($value) { + return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value; + }, $this->items); + } + + public function all() + { + return $this->items; + } + + /** + * 合并数组 + * + * @access public + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 交换数组中的键和值 + * + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, &$indexKey = null) + { + if ($items instanceof self || $items instanceof Paginator) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数组,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } + + /** + * 返回数组中所有的键名 + * + * @access public + * @return array + */ + public function keys() + { + $current = current($this->items); + + if (is_scalar($current)) { + $array = $this->items; + } elseif (is_array($current)) { + $array = $current; + } else { + $array = $current->toArray(); + } + + return array_keys($array); + } + + /** + * 删除数组的最后一个元素(出栈) + * + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value + * @param mixed $key + * @return void + */ + public function push($value, $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 把一个数组分割为新的数组块. + * + * @access public + * @param int $size + * @param bool $preserveKeys + * @return static + */ + public function chunk($size, $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value + * @param mixed $key + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数处理数组中的元素 + * @access public + * @param callable|null $callback + * @return static + */ + public function map(callable $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * 用回调函数过滤数组中的元素 + * @access public + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * 根据字段条件过滤数组中的元素 + * @access public + * @param string $field 字段名 + * @param mixed $operator 操作符 + * @param mixed $value 数据 + * @return static + */ + public function where($field, $operator, $value = null) + { + if (is_null($value)) { + $value = $operator; + $operator = '='; + } + + return $this->filter(function ($data) use ($field, $operator, $value) { + if (strpos($field, '.')) { + list($field, $relation) = explode('.', $field); + + $result = isset($data[$field][$relation]) ? $data[$field][$relation] : null; + } else { + $result = isset($data[$field]) ? $data[$field] : null; + } + + switch (strtolower($operator)) { + case '===': + return $result === $value; + case '!==': + return $result !== $value; + case '!=': + case '<>': + return $result != $value; + case '>': + return $result > $value; + case '>=': + return $result >= $value; + case '<': + return $result < $value; + case '<=': + return $result <= $value; + case 'like': + return is_string($result) && false !== strpos($result, $value); + case 'not like': + return is_string($result) && false === strpos($result, $value); + case 'in': + return is_scalar($result) && in_array($result, $value, true); + case 'not in': + return is_scalar($result) && !in_array($result, $value, true); + case 'between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result >= $min && $result <= $max; + case 'not between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result > $max || $result < $min; + case '==': + case '=': + default: + return $result == $value; + } + }); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param mixed $columnKey 键名 + * @param mixed $indexKey 作为索引值的列 + * @return array + */ + public function column($columnKey, $indexKey = null) + { + return array_column($this->toArray(), $columnKey, $indexKey); + } + + /** + * 对数组排序 + * + * @access public + * @param callable|null $callback + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + + $callback = $callback ?: function ($a, $b) { + return $a == $b ? 0 : (($a < $b) ? -1 : 1); + + }; + + uasort($items, $callback); + + return new static($items); + } + + /** + * 指定字段排序 + * @access public + * @param string $field 排序字段 + * @param string $order 排序 + * @param bool $intSort 是否为数字排序 + * @return $this + */ + public function order($field, $order = null, $intSort = true) + { + return $this->sort(function ($a, $b) use ($field, $order, $intSort) { + $fieldA = isset($a[$field]) ? $a[$field] : null; + $fieldB = isset($b[$field]) ? $b[$field] : null; + + if ($intSort) { + return 'desc' == strtolower($order) ? $fieldB >= $fieldA : $fieldA >= $fieldB; + } else { + return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB); + } + }); + } + + /** + * 将数组打乱 + * + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + + return new static($items); + } + + /** + * 截取数组 + * + * @access public + * @param int $offset + * @param int $length + * @param bool $preserveKeys + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + // ArrayAccess + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + //Countable + public function count() + { + return count($this->items); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->items); + } + + //JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + /** + * 转换成数组 + * + * @access public + * @param mixed $items + * @return array + */ + protected function convertToArray($items) + { + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Config.php b/Server/application/common/Server/thinkphp/library/think/Config.php new file mode 100644 index 00000000..bec6222a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Config.php @@ -0,0 +1,398 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use Yaconf; + +class Config implements \ArrayAccess +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 配置前缀 + * @var string + */ + protected $prefix = 'app'; + + /** + * 配置文件目录 + * @var string + */ + protected $path; + + /** + * 配置文件后缀 + * @var string + */ + protected $ext; + + /** + * 是否支持Yaconf + * @var bool + */ + protected $yaconf; + + /** + * 构造方法 + * @access public + */ + public function __construct($path = '', $ext = '.php') + { + $this->path = $path; + $this->ext = $ext; + $this->yaconf = class_exists('Yaconf'); + } + + public static function __make(App $app) + { + $path = $app->getConfigPath(); + $ext = $app->getConfigExt(); + return new static($path, $ext); + } + + /** + * 设置开启Yaconf + * @access public + * @param bool|string $yaconf 是否使用Yaconf + * @return void + */ + public function setYaconf($yaconf) + { + if ($this->yaconf) { + $this->yaconf = $yaconf; + } + } + + /** + * 设置配置参数默认前缀 + * @access public + * @param string $prefix 前缀 + * @return void + */ + public function setDefaultPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * 解析配置文件或内容 + * @access public + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) + * @return mixed + */ + public function parse($config, $type = '', $name = '') + { + if (empty($type)) { + $type = pathinfo($config, PATHINFO_EXTENSION); + } + + $object = Loader::factory($type, '\\think\\config\\driver\\', $config); + + return $this->set($object->parse(), $name); + } + + /** + * 加载配置文件(多种格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return mixed + */ + public function load($file, $name = '') + { + if (is_file($file)) { + $filename = $file; + } elseif (is_file($this->path . $file . $this->ext)) { + $filename = $this->path . $file . $this->ext; + } + + if (isset($filename)) { + return $this->loadFile($filename, $name); + } elseif ($this->yaconf && Yaconf::has($file)) { + return $this->set(Yaconf::get($file), $name); + } + + return $this->config; + } + + /** + * 获取实际的yaconf配置参数 + * @access protected + * @param string $name 配置参数名 + * @return string + */ + protected function getYaconfName($name) + { + if ($this->yaconf && is_string($this->yaconf)) { + return $this->yaconf . '.' . $name; + } + + return $name; + } + + /** + * 获取yaconf配置 + * @access public + * @param string $name 配置参数名 + * @param mixed $default 默认值 + * @return mixed + */ + public function yaconf($name, $default = null) + { + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + return Yaconf::get($yaconfName); + } + } + + return $default; + } + + protected function loadFile($file, $name) + { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + + if ('php' == $type) { + return $this->set(include $file, $name); + } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { + return $this->set(yaml_parse_file($file), $name); + } + + return $this->parse($file, $type, $name); + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @return bool + */ + public function has($name) + { + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + return !is_null($this->get($name)); + } + + /** + * 获取一级配置 + * @access public + * @param string $name 一级配置名 + * @return array + */ + public function pull($name) + { + $name = strtolower($name); + + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + $config = Yaconf::get($yaconfName); + return isset($this->config[$name]) ? array_merge($this->config[$name], $config) : $config; + } + } + + return isset($this->config[$name]) ? $this->config[$name] : []; + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name = null, $default = null) + { + if ($name && false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + // 无参数时获取所有 + if (empty($name)) { + return $this->config; + } + + if ('.' == substr($name, -1)) { + return $this->pull(substr($name, 0, -1)); + } + + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + return Yaconf::get($yaconfName); + } + } + + $name = explode('.', $name); + $name[0] = strtolower($name[0]); + $config = $this->config; + + // 按.拆分成多维数组进行判断 + foreach ($name as $val) { + if (isset($config[$val])) { + $config = $config[$val]; + } else { + return $default; + } + } + + return $config; + } + + /** + * 设置配置参数 name为数组则为批量设置 + * @access public + * @param string|array $name 配置参数名(支持三级配置 .号分割) + * @param mixed $value 配置值 + * @return mixed + */ + public function set($name, $value = null) + { + if (is_string($name)) { + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + $this->config[strtolower($name[0])][$name[1]] = $value; + } else { + $this->config[strtolower($name[0])][$name[1]][$name[2]] = $value; + } + + return $value; + } elseif (is_array($name)) { + // 批量设置 + if (!empty($value)) { + if (isset($this->config[$value])) { + $result = array_merge($this->config[$value], $name); + } else { + $result = $name; + } + + $this->config[$value] = $result; + } else { + $result = $this->config = array_merge($this->config, $name); + } + } else { + // 为空直接返回 已有配置 + $result = $this->config; + } + + return $result; + } + + /** + * 移除配置 + * @access public + * @param string $name 配置参数名(支持三级配置 .号分割) + * @return void + */ + public function remove($name) + { + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + unset($this->config[strtolower($name[0])][$name[1]]); + } else { + unset($this->config[strtolower($name[0])][$name[1]][$name[2]]); + } + } + + /** + * 重置配置参数 + * @access public + * @param string $prefix 配置前缀名 + * @return void + */ + public function reset($prefix = '') + { + if ('' === $prefix) { + $this->config = []; + } else { + $this->config[$prefix] = []; + } + } + + /** + * 设置配置 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set($name, $value) + { + return $this->set($name, $value); + } + + /** + * 获取配置参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * 检测是否存在参数 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function __isset($name) + { + return $this->has($name); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->set($name, $value); + } + + public function offsetExists($name) + { + return $this->has($name); + } + + public function offsetUnset($name) + { + $this->remove($name); + } + + public function offsetGet($name) + { + return $this->get($name); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Console.php b/Server/application/common/Server/thinkphp/library/think/Console.php new file mode 100644 index 00000000..22f3e2c5 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Console.php @@ -0,0 +1,829 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Command; +use think\console\command\Help as HelpCommand; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\driver\Buffer; + +class Console +{ + + private $name; + private $version; + + /** @var Command[] */ + private $commands = []; + + private $wantHelps = false; + + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $defaultCommand; + + private static $defaultCommands = [ + 'help' => "think\\console\\command\\Help", + 'list' => "think\\console\\command\\Lists", + 'build' => "think\\console\\command\\Build", + 'clear' => "think\\console\\command\\Clear", + 'make:command' => "think\\console\\command\\make\\Command", + 'make:controller' => "think\\console\\command\\make\\Controller", + 'make:model' => "think\\console\\command\\make\\Model", + 'make:middleware' => "think\\console\\command\\make\\Middleware", + 'make:validate' => "think\\console\\command\\make\\Validate", + 'optimize:autoload' => "think\\console\\command\\optimize\\Autoload", + 'optimize:config' => "think\\console\\command\\optimize\\Config", + 'optimize:schema' => "think\\console\\command\\optimize\\Schema", + 'optimize:route' => "think\\console\\command\\optimize\\Route", + 'run' => "think\\console\\command\\RunServer", + 'version' => "think\\console\\command\\Version", + 'route:list' => "think\\console\\command\\RouteList", + ]; + + /** + * Console constructor. + * @access public + * @param string $name 名称 + * @param string $version 版本 + * @param null|string $user 执行用户 + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) + { + $this->name = $name; + $this->version = $version; + + if ($user) { + $this->setUser($user); + } + + $this->defaultCommand = 'list'; + $this->definition = $this->getDefaultInputDefinition(); + } + + /** + * 设置执行用户 + * @param $user + */ + public function setUser($user) + { + if (DIRECTORY_SEPARATOR == '\\') { + return; + } + + $user = posix_getpwnam($user); + if ($user) { + posix_setuid($user['uid']); + posix_setgid($user['gid']); + } + } + + /** + * 初始化 Console + * @access public + * @param bool $run 是否运行 Console + * @return int|Console + */ + public static function init($run = true) + { + static $console; + + if (!$console) { + $config = Container::get('config')->pull('console'); + $console = new self($config['name'], $config['version'], $config['user']); + + $commands = $console->getDefinedCommands($config); + + // 添加指令集 + $console->addCommands($commands); + } + + if ($run) { + // 运行 + return $console->run(); + } else { + return $console; + } + } + + /** + * @access public + * @param array $config + * @return array + */ + public function getDefinedCommands(array $config = []) + { + $commands = self::$defaultCommands; + + if (!empty($config['auto_path']) && is_dir($config['auto_path'])) { + // 自动加载指令类 + $files = scandir($config['auto_path']); + + if (count($files) > 2) { + $beforeClass = get_declared_classes(); + + foreach ($files as $file) { + if (pathinfo($file, PATHINFO_EXTENSION) == 'php') { + include $config['auto_path'] . $file; + } + } + + $afterClass = get_declared_classes(); + $commands = array_merge($commands, array_diff($afterClass, $beforeClass)); + } + } + + $file = Container::get('env')->get('app_path') . 'command.php'; + + if (is_file($file)) { + $appCommands = include $file; + + if (is_array($appCommands)) { + $commands = array_merge($commands, $appCommands); + } + } + + return $commands; + } + + /** + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output|Buffer + */ + public static function call($command, array $parameters = [], $driver = 'buffer') + { + $console = self::init(false); + + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $console->setCatchExceptions(false); + $console->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + * @api + */ + public function run() + { + $input = new Input(); + $output = new Output(); + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + $output->renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * 执行指令 + * @access public + * @param Input $input + * @param Output $output + * @return int + */ + public function doRun(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--version', '-V'])) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + + if (true === $input->hasParameterOption(['--help', '-h'])) { + if (!$name) { + $name = 'help'; + $input = new Input(['help']); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $input = new Input([$this->defaultCommand]); + } + + $command = $this->find($name); + + $exitCode = $this->doRunCommand($command, $input, $output); + + return $exitCode; + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the help message. + * @access public + * @return string A help message. + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * 是否捕获异常 + * @access public + * @param bool $boolean + * @api + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + } + + /** + * 是否自动退出 + * @access public + * @param bool $boolean + * @api + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + } + + /** + * 获取名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置名称 + * @access public + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * 获取版本 + * @access public + * @return string + * @api + */ + public function getVersion() + { + return $this->version; + } + + /** + * 设置版本 + * @access public + * @param string $version + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf('%s version %s', $this->getName(), $this->getVersion()); + } + + return 'Console Tool'; + } + + /** + * 注册一个指令 (便于动态创建指令) + * @access public + * @param string $name 指令名 + * @return Command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * 添加指令集 + * @access public + * @param array $commands + */ + public function addCommands(array $commands) + { + foreach ($commands as $key => $command) { + if (is_subclass_of($command, "\\think\\console\\Command")) { + // 注册指令 + $this->add($command, is_numeric($key) ? '' : $key); + } + } + } + + /** + * 注册一个指令(对象) + * @access public + * @param mixed $command 指令对象或者指令类名 + * @param string $name 指令名 留空则自动获取 + * @return mixed + */ + public function add($command, $name) + { + if ($name) { + $this->commands[$name] = $command; + return; + } + + if (is_string($command)) { + $command = new $command(); + } + + $command->setConsole($this); + + if (!$command->isEnabled()) { + $command->setConsole(null); + return; + } + + if (null === $command->getDefinition()) { + throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * 获取指令 + * @access public + * @param string $name 指令名称 + * @return Command + * @throws \InvalidArgumentException + */ + public function get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if (is_string($command)) { + $command = new $command(); + } + + $command->setConsole($this); + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces() + { + $namespaces = []; + foreach ($this->commands as $name => $command) { + if (is_string($command)) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($name)); + } else { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * 查找注册命名空间中的名称或缩写。 + * @access public + * @param string $namespace + * @return string + * @throws \InvalidArgumentException + */ + public function findNamespace($namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * 查找指令 + * @access public + * @param string $name 名称或者别名 + * @return Command + * @throws \InvalidArgumentException + */ + public function find($name) + { + $allCommands = array_keys($this->commands); + + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $commands = preg_grep('{^' . $expr . '}', $allCommands); + + if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + * @api + */ + public function all($namespace = null) + { + if (null === $namespace) { + return $this->commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * 获取可能的指令名 + * @access public + * @param array $names + * @return array + */ + public static function getAbbreviations($names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例。 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + */ + protected function configureIO(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--ansi'])) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'])) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { + $input->setInteractive(false); + } + + if (true === $input->hasParameterOption(['--quiet', '-q'])) { + $output->setVerbosity(Output::VERBOSITY_QUIET); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + } + } + } + + /** + * 执行指令 + * @access protected + * @param Command $command 指令实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return int + * @throws \Exception + */ + protected function doRunCommand(Command $command, Input $input, Output $output) + { + return $command->run($input, $output); + } + + /** + * 获取指令的基础名称 + * @access protected + * @param Input $input + * @return string + */ + protected function getCommandName(Input $input) + { + return $input->getFirstArgument(); + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + 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'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + public static function addDefaultCommands(array $classnames) + { + self::$defaultCommands = array_merge(self::$defaultCommands, $classnames); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions($abbrevs) + { + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); + } + + /** + * 返回命名空间部分 + * @access public + * @param string $name 指令 + * @param string $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name + * @param array|\Traversable $collection + * @return array + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { + return $lev < 2 * $threshold; + }); + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * 设置默认的指令 + * @access public + * @param string $commandName The Command name + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name + * @return array + */ + private function extractAllNamespaces($name) + { + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['commands'], $data['definition']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Container.php b/Server/application/common/Server/thinkphp/library/think/Container.php new file mode 100644 index 00000000..91b32aa6 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Container.php @@ -0,0 +1,618 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use InvalidArgumentException; +use IteratorAggregate; +use ReflectionClass; +use ReflectionException; +use ReflectionFunction; +use ReflectionMethod; +use think\exception\ClassNotFoundException; + +/** + * @package think + * @property Build $build + * @property Cache $cache + * @property Config $config + * @property Cookie $cookie + * @property Debug $debug + * @property Env $env + * @property Hook $hook + * @property Lang $lang + * @property Middleware $middleware + * @property Request $request + * @property Response $response + * @property Route $route + * @property Session $session + * @property Template $template + * @property Url $url + * @property Validate $validate + * @property View $view + * @property route\RuleName $rule_name + * @property Log $log + */ +class Container implements ArrayAccess, IteratorAggregate, Countable +{ + /** + * 容器对象实例 + * @var Container + */ + protected static $instance; + + /** + * 容器中的对象实例 + * @var array + */ + protected $instances = []; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = [ + 'app' => App::class, + 'build' => Build::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'cookie' => Cookie::class, + 'debug' => Debug::class, + 'env' => Env::class, + 'hook' => Hook::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'middleware' => Middleware::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'template' => Template::class, + 'url' => Url::class, + 'validate' => Validate::class, + 'view' => View::class, + 'rule_name' => route\RuleName::class, + // 接口依赖注入 + 'think\LoggerInterface' => Log::class, + ]; + + /** + * 容器标识别名 + * @var array + */ + protected $name = []; + + /** + * 获取当前容器的实例(单例) + * @access public + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + return static::$instance; + } + + /** + * 设置当前容器的实例 + * @access public + * @param object $instance + * @return void + */ + public static function setInstance($instance) + { + static::$instance = $instance; + } + + /** + * 获取容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function get($abstract, $vars = [], $newInstance = false) + { + return static::getInstance()->make($abstract, $vars, $newInstance); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + public static function set($abstract, $concrete = null) + { + return static::getInstance()->bindTo($abstract, $concrete); + } + + /** + * 移除容器中的对象实例 + * @access public + * @param string $abstract 类标识、接口 + * @return void + */ + public static function remove($abstract) + { + return static::getInstance()->delete($abstract); + } + + /** + * 清除容器中的对象实例 + * @access public + * @return void + */ + public static function clear() + { + return static::getInstance()->flush(); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string|array $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return $this + */ + public function bindTo($abstract, $concrete = null) + { + if (is_array($abstract)) { + $this->bind = array_merge($this->bind, $abstract); + } elseif ($concrete instanceof Closure) { + $this->bind[$abstract] = $concrete; + } elseif (is_object($concrete)) { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + $this->instances[$abstract] = $concrete; + } else { + $this->bind[$abstract] = $concrete; + } + + return $this; + } + + /** + * 绑定一个类实例当容器 + * @access public + * @param string $abstract 类名或者标识 + * @param object|\Closure $instance 类的实例 + * @return $this + */ + public function instance($abstract, $instance) + { + if ($instance instanceof \Closure) { + $this->bind[$abstract] = $instance; + } else { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + + $this->instances[$abstract] = $instance; + } + + return $this; + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function bound($abstract) + { + return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function exists($abstract) + { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + + return isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $name 类名或者标识 + * @return bool + */ + public function has($name) + { + return $this->bound($name); + } + + /** + * 创建类的实例 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public function make($abstract, $vars = [], $newInstance = false) + { + if (true === $vars) { + // 总是创建新的实例化对象 + $newInstance = true; + $vars = []; + } + + $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; + + if (isset($this->instances[$abstract]) && !$newInstance) { + return $this->instances[$abstract]; + } + + if (isset($this->bind[$abstract])) { + $concrete = $this->bind[$abstract]; + + if ($concrete instanceof Closure) { + $object = $this->invokeFunction($concrete, $vars); + } else { + $this->name[$abstract] = $concrete; + return $this->make($concrete, $vars, $newInstance); + } + } else { + $object = $this->invokeClass($abstract, $vars); + } + + if (!$newInstance) { + $this->instances[$abstract] = $object; + } + + return $object; + } + + /** + * 删除容器中的对象实例 + * @access public + * @param string|array $abstract 类名或者标识 + * @return void + */ + public function delete($abstract) + { + foreach ((array) $abstract as $name) { + $name = isset($this->name[$name]) ? $this->name[$name] : $name; + + if (isset($this->instances[$name])) { + unset($this->instances[$name]); + } + } + } + + /** + * 获取容器中的对象实例 + * @access public + * @return array + */ + public function all() + { + return $this->instances; + } + + /** + * 清除容器中的对象实例 + * @access public + * @return void + */ + public function flush() + { + $this->instances = []; + $this->bind = []; + $this->name = []; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param mixed $function 函数或者闭包 + * @param array $vars 参数 + * @return mixed + */ + public function invokeFunction($function, $vars = []) + { + try { + $reflect = new ReflectionFunction($function); + + $args = $this->bindParams($reflect, $vars); + + return call_user_func_array($function, $args); + } catch (ReflectionException $e) { + throw new Exception('function not exists: ' . $function . '()'); + } + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param mixed $method 方法 + * @param array $vars 参数 + * @return mixed + */ + public function invokeMethod($method, $vars = []) + { + try { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]); + $reflect = new ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new ReflectionMethod($method); + } + + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } catch (ReflectionException $e) { + if (is_array($method) && is_object($method[0])) { + $method[0] = get_class($method[0]); + } + + throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()'); + } + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param object $instance 对象实例 + * @param mixed $reflect 反射类 + * @param array $vars 参数 + * @return mixed + */ + public function invokeReflectMethod($instance, $reflect, $vars = []) + { + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($instance, $args); + } + + /** + * 调用反射执行callable 支持参数绑定 + * @access public + * @param mixed $callable + * @param array $vars 参数 + * @return mixed + */ + public function invoke($callable, $vars = []) + { + if ($callable instanceof Closure) { + return $this->invokeFunction($callable, $vars); + } + + return $this->invokeMethod($callable, $vars); + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 参数 + * @return mixed + */ + public function invokeClass($class, $vars = []) + { + try { + $reflect = new ReflectionClass($class); + + if ($reflect->hasMethod('__make')) { + $method = new ReflectionMethod($class, '__make'); + + if ($method->isPublic() && $method->isStatic()) { + $args = $this->bindParams($method, $vars); + return $method->invokeArgs(null, $args); + } + } + + $constructor = $reflect->getConstructor(); + + $args = $constructor ? $this->bindParams($constructor, $vars) : []; + + return $reflect->newInstanceArgs($args); + + } catch (ReflectionException $e) { + throw new ClassNotFoundException('class not exists: ' . $class, $class); + } + } + + /** + * 绑定参数 + * @access protected + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 参数 + * @return array + */ + protected function bindParams($reflect, $vars = []) + { + if ($reflect->getNumberOfParameters() == 0) { + return []; + } + + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); + + if (PHP_VERSION > 8.0) { + $args = $this->parseParamsForPHP8($params, $vars, $type); + } else { + $args = $this->parseParams($params, $vars, $type); + } + + return $args; + } + + /** + * 解析参数 + * @access protected + * @param array $params 参数列表 + * @param array $vars 参数数据 + * @param int $type 参数类别 + * @return array + */ + protected function parseParams($params, $vars, $type) + { + foreach ($params as $param) { + $name = $param->getName(); + $lowerName = Loader::parseName($name); + $class = $param->getClass(); + + if ($class) { + $args[] = $this->getObjectParam($class->getName(), $vars); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $args[] = $vars[$name]; + } elseif (0 == $type && isset($vars[$lowerName])) { + $args[] = $vars[$lowerName]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); + } + } + return $args; + } + + /** + * 解析参数 + * @access protected + * @param array $params 参数列表 + * @param array $vars 参数数据 + * @param int $type 参数类别 + * @return array + */ + protected function parseParamsForPHP8($params, $vars, $type) + { + foreach ($params as $param) { + $name = $param->getName(); + $lowerName = Loader::parseName($name); + $reflectionType = $param->getType(); + + if ($reflectionType && $reflectionType->isBuiltin() === false) { + $args[] = $this->getObjectParam($reflectionType->getName(), $vars); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && array_key_exists($name, $vars)) { + $args[] = $vars[$name]; + } elseif (0 == $type && array_key_exists($lowerName, $vars)) { + $args[] = $vars[$lowerName]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); + } + } + return $args; + } + + /** + * 获取对象类型的参数值 + * @access protected + * @param string $className 类名 + * @param array $vars 参数 + * @return mixed + */ + protected function getObjectParam($className, &$vars) + { + $array = $vars; + $value = array_shift($array); + + if ($value instanceof $className) { + $result = $value; + array_shift($vars); + } else { + $result = $this->make($className); + } + + return $result; + } + + public function __set($name, $value) + { + $this->bindTo($name, $value); + } + + public function __get($name) + { + return $this->make($name); + } + + public function __isset($name) + { + return $this->bound($name); + } + + public function __unset($name) + { + $this->delete($name); + } + + public function offsetExists($key) + { + return $this->__isset($key); + } + + public function offsetGet($key) + { + return $this->__get($key); + } + + public function offsetSet($key, $value) + { + $this->__set($key, $value); + } + + public function offsetUnset($key) + { + $this->__unset($key); + } + + //Countable + public function count() + { + return count($this->instances); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->instances); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['instances'], $data['instance']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Controller.php b/Server/application/common/Server/thinkphp/library/think/Controller.php new file mode 100644 index 00000000..966eaaa8 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Controller.php @@ -0,0 +1,287 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ValidateException; +use traits\controller\Jump; + +class Controller +{ + use Jump; + + /** + * 视图类实例 + * @var \think\View + */ + protected $view; + + /** + * Request实例 + * @var \think\Request + */ + protected $request; + + /** + * 验证失败是否抛出异常 + * @var bool + */ + protected $failException = false; + + /** + * 是否批量验证 + * @var bool + */ + protected $batchValidate = false; + + /** + * 前置操作方法列表(即将废弃) + * @var array $beforeActionList + */ + protected $beforeActionList = []; + + /** + * 控制器中间件 + * @var array + */ + protected $middleware = []; + + /** + * 构造方法 + * @access public + */ + public function __construct(App $app = null) + { + $this->app = $app ?: Container::get('app'); + $this->request = $this->app['request']; + $this->view = $this->app['view']; + + // 控制器初始化 + $this->initialize(); + + $this->registerMiddleware(); + + // 前置操作方法 即将废弃 + foreach ((array) $this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); + } + } + + // 初始化 + protected function initialize() + {} + + // 注册控制器中间件 + public function registerMiddleware() + { + foreach ($this->middleware as $key => $val) { + if (!is_int($key)) { + $only = $except = null; + + if (isset($val['only'])) { + $only = array_map(function ($item) { + return strtolower($item); + }, $val['only']); + } elseif (isset($val['except'])) { + $except = array_map(function ($item) { + return strtolower($item); + }, $val['except']); + } + + if (isset($only) && !in_array($this->request->action(), $only)) { + continue; + } elseif (isset($except) && in_array($this->request->action(), $except)) { + continue; + } else { + $val = $key; + } + } + + $this->app['middleware']->controller($val); + } + } + + /** + * 前置操作 + * @access protected + * @param string $method 前置操作方法名 + * @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]] + */ + protected function beforeAction($method, $options = []) + { + if (isset($options['only'])) { + if (is_string($options['only'])) { + $options['only'] = explode(',', $options['only']); + } + + $only = array_map(function ($val) { + return strtolower($val); + }, $options['only']); + + if (!in_array($this->request->action(), $only)) { + return; + } + } elseif (isset($options['except'])) { + if (is_string($options['except'])) { + $options['except'] = explode(',', $options['except']); + } + + $except = array_map(function ($val) { + return strtolower($val); + }, $options['except']); + + if (in_array($this->request->action(), $except)) { + return; + } + } + + call_user_func([$this, $method]); + } + + /** + * 加载模板输出 + * @access protected + * @param string $template 模板文件名 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @return mixed + */ + protected function fetch($template = '', $vars = [], $config = []) + { + return Response::create($template, 'view')->assign($vars)->config($config); + } + + /** + * 渲染内容输出 + * @access protected + * @param string $content 模板内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @return mixed + */ + protected function display($content = '', $vars = [], $config = []) + { + return Response::create($content, 'view')->assign($vars)->config($config)->isContent(true); + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return $this + */ + protected function assign($name, $value = '') + { + $this->view->assign($name, $value); + + return $this; + } + + /** + * 视图过滤 + * @access protected + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + protected function filter($filter) + { + $this->view->filter($filter); + + return $this; + } + + /** + * 初始化模板引擎 + * @access protected + * @param array|string $engine 引擎参数 + * @return $this + */ + protected function engine($engine) + { + $this->view->engine($engine); + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + protected function validateFailException($fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @param mixed $callback 回调方法(闭包) + * @return array|string|true + * @throws ValidateException + */ + protected function validate($data, $validate, $message = [], $batch = false, $callback = null) + { + if (is_array($validate)) { + $v = $this->app->validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + list($validate, $scene) = explode('.', $validate); + } + $v = $this->app->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } + } + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + if (is_array($message)) { + $v->message($message); + } + + if ($callback && is_callable($callback)) { + call_user_func_array($callback, [$v, &$data]); + } + + if (!$v->check($data)) { + if ($this->failException) { + throw new ValidateException($v->getError()); + } + return $v->getError(); + } + + return true; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Cookie.php b/Server/application/common/Server/thinkphp/library/think/Cookie.php new file mode 100644 index 00000000..6a9fb1ee --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Cookie.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Cookie +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, + ]; + + /** + * 构造方法 + * @access public + */ + public function __construct(array $config = []) + { + $this->init($config); + } + + /** + * Cookie初始化 + * @access public + * @param array $config + * @return void + */ + public function init(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + + if (!empty($this->config['httponly']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.cookie_httponly', 1); + } + } + + public static function __make(Config $config) + { + return new static($config->pull('cookie')); + } + + /** + * 设置或者获取cookie作用域(前缀) + * @access public + * @param string $prefix + * @return string|void + */ + public function prefix($prefix = '') + { + if (empty($prefix)) { + return $this->config['prefix']; + } + + $this->config['prefix'] = $prefix; + } + + /** + * Cookie 设置、获取、删除 + * + * @access public + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function set($name, $value = '', $option = null) + { + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) { + $option = ['expire' => $option]; + } elseif (is_string($option)) { + parse_str($option, $option); + } + + $config = array_merge($this->config, array_change_key_case($option)); + } else { + $config = $this->config; + } + + $name = $config['prefix'] . $name; + + // 设置cookie + if (is_array($value)) { + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'encode'); + $value = 'think:' . json_encode($value); + } + + $expire = !empty($config['expire']) ? $_SERVER['REQUEST_TIME'] + intval($config['expire']) : 0; + + if ($config['setcookie']) { + $this->setCookie($name, $value, $expire, $config); + } + + $_COOKIE[$name] = $value; + } + + /** + * Cookie 设置保存 + * + * @access public + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param array $option 可选参数 + * @return void + */ + protected function setCookie($name, $value, $expire, $option = []) + { + setcookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'], $option['httponly']); + } + + /** + * 永久保存Cookie数据 + * @access public + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + $this->set($name, $value, $option); + } + + /** + * 判断Cookie数据 + * @access public + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 + * @return bool + */ + public function has($name, $prefix = null) + { + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $name = $prefix . $name; + + return isset($_COOKIE[$name]); + } + + /** + * Cookie获取 + * @access public + * @param string $name cookie名称 留空获取全部 + * @param string|null $prefix cookie前缀 + * @return mixed + */ + public function get($name = '', $prefix = null) + { + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $key = $prefix . $name; + + if ('' == $name) { + if ($prefix) { + $value = []; + foreach ($_COOKIE as $k => $val) { + if (0 === strpos($k, $prefix)) { + $value[$k] = $val; + } + } + } else { + $value = $_COOKIE; + } + } elseif (isset($_COOKIE[$key])) { + $value = $_COOKIE[$key]; + + if (0 === strpos($value, 'think:')) { + $value = substr($value, 6); + $value = json_decode($value, true); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'decode'); + } + } else { + $value = null; + } + + return $value; + } + + /** + * Cookie删除 + * @access public + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 + * @return void + */ + public function delete($name, $prefix = null) + { + $config = $this->config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + $name = $prefix . $name; + + if ($config['setcookie']) { + $this->setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, $config); + } + + // 删除指定cookie + unset($_COOKIE[$name]); + } + + /** + * Cookie清空 + * @access public + * @param string|null $prefix cookie前缀 + * @return void + */ + public function clear($prefix = null) + { + // 清除指定前缀的所有cookie + if (empty($_COOKIE)) { + return; + } + + // 要删除的cookie前缀,不指定则删除config设置的指定前缀 + $config = $this->config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + + if ($prefix) { + // 如果前缀为空字符串将不作处理直接返回 + foreach ($_COOKIE as $key => $val) { + if (0 === strpos($key, $prefix)) { + if ($config['setcookie']) { + $this->setcookie($key, '', $_SERVER['REQUEST_TIME'] - 3600, $config); + } + unset($_COOKIE[$key]); + } + } + } + + return; + } + + private function jsonFormatProtect(&$val, $key, $type = 'encode') + { + if (!empty($val) && true !== $val) { + $val = 'decode' == $type ? urldecode($val) : urlencode($val); + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/Db.php b/Server/application/common/Server/thinkphp/library/think/Db.php new file mode 100644 index 00000000..9280eac0 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Db.php @@ -0,0 +1,197 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\db\Connection; + +/** + * Class Db + * @package think + * @method \think\db\Query master() static 从主服务器读取数据 + * @method \think\db\Query readMaster(bool $all = false) static 后续从主服务器读取数据 + * @method \think\db\Query table(string $table) static 指定数据表(含前缀) + * @method \think\db\Query name(string $name) static 指定数据表(不含前缀) + * @method \think\db\Expression raw(string $value) static 使用表达式设置数据 + * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method \think\db\Query whereRaw(string $where, array $bind = []) static 表达式查询 + * @method \think\db\Query whereExp(string $field, string $condition, array $bind = []) static 字段表达式查询 + * @method \think\db\Query when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询 + * @method \think\db\Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method \think\db\Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method \think\db\Query field(mixed $field, boolean $except = false) static 指定查询字段 + * @method \think\db\Query fieldRaw(string $field, array $bind = []) static 指定查询字段 + * @method \think\db\Query union(mixed $union, boolean $all = false) static UNION查询 + * @method \think\db\Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method \think\db\Query order(mixed $field, string $order = null) static 查询ORDER + * @method \think\db\Query orderRaw(string $field, array $bind = []) static 查询ORDER + * @method \think\db\Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method \think\db\Query withAttr(string $name,callable $callback = null) static 使用获取器获取数据 + * @method mixed value(string $field) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 + * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 + * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID + * @method integer insertAll(array $dataSet) static 插入多条记录 + * @method integer update(array $data) static 更新记录 + * @method integer delete(mixed $data = null) static 删除记录 + * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 + * @method \Generator cursor(mixed $data = null) static 使用游标查找记录 + * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 + * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 + * @method \think\Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 + * @method mixed transaction(callable $callback) static 执行数据库事务 + * @method void startTrans() static 启动事务 + * @method void commit() static 用于非自动提交状态下面的查询提交 + * @method void rollback() static 事务回滚 + * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 + * @method string getLastInsID(string $sequence = null) static 获取最近插入的ID + */ +class Db +{ + /** + * 当前数据库连接对象 + * @var Connection + */ + protected static $connection; + + /** + * 数据库配置 + * @var array + */ + protected static $config = []; + + /** + * 查询次数 + * @var integer + */ + public static $queryTimes = 0; + + /** + * 执行次数 + * @var integer + */ + public static $executeTimes = 0; + + /** + * 配置 + * @access public + * @param mixed $config + * @return void + */ + public static function init($config = []) + { + self::$config = $config; + + if (empty($config['query'])) { + self::$config['query'] = '\\think\\db\\Query'; + } + } + + /** + * 获取数据库配置 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public static function getConfig($name = '') + { + if ('' === $name) { + return self::$config; + } + + return isset(self::$config[$name]) ? self::$config[$name] : null; + } + + /** + * 切换数据库连接 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @param string $query 查询对象类名 + * @return mixed 返回查询对象实例 + * @throws Exception + */ + public static function connect($config = [], $name = false, $query = '') + { + // 解析配置参数 + $options = self::parseConfig($config ?: self::$config); + + $query = $query ?: $options['query']; + + // 创建数据库连接对象实例 + self::$connection = Connection::instance($options, $name); + + return new $query(self::$connection); + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config + * @return array + */ + private static function parseConfig($config) + { + if (is_string($config) && false === strpos($config, '/')) { + // 支持读取配置参数 + $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config; + } + + $result = is_string($config) ? self::parseDsnConfig($config) : $config; + + if (empty($result['query'])) { + $result['query'] = self::$config['query']; + } + + return $result; + } + + /** + * DSN解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr + * @return array + */ + private static function parseDsnConfig($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + + public static function __callStatic($method, $args) + { + return call_user_func_array([static::connect(), $method], $args); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Debug.php b/Server/application/common/Server/thinkphp/library/think/Debug.php new file mode 100644 index 00000000..776e1787 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Debug.php @@ -0,0 +1,278 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\model\Collection as ModelCollection; +use think\response\Redirect; + +class Debug +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 区间时间信息 + * @var array + */ + protected $info = []; + + /** + * 区间内存信息 + * @var array + */ + protected $mem = []; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = $config; + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('trace')); + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 记录时间(微秒)和内存使用情况 + * @access public + * @param string $name 标记位置 + * @param mixed $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存 + * @return void + */ + public function remark($name, $value = '') + { + // 记录时间和内存使用 + $this->info[$name] = is_float($value) ? $value : microtime(true); + + if ('time' != $value) { + $this->mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + $this->mem['peak'][$name] = memory_get_peak_usage(); + } + } + + /** + * 统计某个区间的时间(微秒)使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return integer + */ + public function getRangeTime($start, $end, $dec = 6) + { + if (!isset($this->info[$end])) { + $this->info[$end] = microtime(true); + } + + return number_format(($this->info[$end] - $this->info[$start]), $dec); + } + + /** + * 统计从开始到统计时的时间(微秒)使用情况 + * @access public + * @param integer|string $dec 小数位 + * @return integer + */ + public function getUseTime($dec = 6) + { + return number_format((microtime(true) - $this->app->getBeginTime()), $dec); + } + + /** + * 获取当前访问的吞吐率情况 + * @access public + * @return string + */ + public function getThroughputRate() + { + return number_format(1 / $this->getUseTime(), 2) . 'req/s'; + } + + /** + * 记录区间的内存使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return string + */ + public function getRangeMem($start, $end, $dec = 2) + { + if (!isset($this->mem['mem'][$end])) { + $this->mem['mem'][$end] = memory_get_usage(); + } + + $size = $this->mem['mem'][$end] - $this->mem['mem'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计从开始到统计时的内存使用情况 + * @access public + * @param integer|string $dec 小数位 + * @return string + */ + public function getUseMem($dec = 2) + { + $size = memory_get_usage() - $this->app->getBeginMem(); + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计区间的内存峰值情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return string + */ + public function getMemPeak($start, $end, $dec = 2) + { + if (!isset($this->mem['peak'][$end])) { + $this->mem['peak'][$end] = memory_get_peak_usage(); + } + + $size = $this->mem['peak'][$end] - $this->mem['peak'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 获取文件加载信息 + * @access public + * @param bool $detail 是否显示详细 + * @return integer|array + */ + public function getFile($detail = false) + { + if ($detail) { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } + + return count(get_included_files()); + } + + /** + * 浏览器友好的变量输出 + * @access public + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @param integer $flags htmlspecialchars flags + * @return void|string + */ + public function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + { + $label = (null === $label) ? '' : rtrim($label) . ':'; + if ($var instanceof Model || $var instanceof ModelCollection) { + $var = $var->toArray(); + } + + ob_start(); + var_dump($var); + + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + + if (PHP_SAPI == 'cli') { + $output = PHP_EOL . $label . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, $flags); + } + $output = '
' . $label . $output . '
'; + } + if ($echo) { + echo($output); + return; + } + return $output; + } + + public function inject(Response $response, &$content) + { + $config = $this->config; + $type = isset($config['type']) ? $config['type'] : 'Html'; + + unset($config['type']); + + $trace = Loader::factory($type, '\\think\\debug\\', $config); + + if ($response instanceof Redirect) { + //TODO 记录 + } else { + $output = $trace->output($response, $this->app['log']->getLog()); + if (is_string($output)) { + // trace调试信息注入 + $pos = strripos($content, ''); + if (false !== $pos) { + $content = substr($content, 0, $pos) . $output . substr($content, $pos); + } else { + $content = $content . $output; + } + } + } + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Env.php b/Server/application/common/Server/thinkphp/library/think/Env.php new file mode 100644 index 00000000..eaeee943 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Env.php @@ -0,0 +1,113 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Env +{ + /** + * 环境变量数据 + * @var array + */ + protected $data = []; + + public function __construct() + { + $this->data = $_ENV; + } + + /** + * 读取环境变量定义文件 + * @access public + * @param string $file 环境变量定义文件 + * @return void + */ + public function load($file) + { + $env = parse_ini_file($file, true); + $this->set($env); + } + + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name = null, $default = null, $php_prefix = true) + { + if (is_null($name)) { + return $this->data; + } + + $name = strtoupper(str_replace('.', '_', $name)); + + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default, $php_prefix); + } + + protected function getEnv($name, $default = null, $php_prefix = true) + { + if ($php_prefix) { + $name = 'PHP_' . $name; + } + + $result = getenv($name); + + if (false === $result) { + return $default; + } + + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; + } + + /** + * 设置环境变量值 + * @access public + * @param string|array $env 环境变量 + * @param mixed $value 值 + * @return void + */ + public function set($env, $value = null) + { + if (is_array($env)) { + $env = array_change_key_case($env, CASE_UPPER); + + foreach ($env as $key => $val) { + if (is_array($val)) { + foreach ($val as $k => $v) { + $this->data[$key . '_' . strtoupper($k)] = $v; + } + } else { + $this->data[$key] = $val; + } + } + } else { + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Error.php b/Server/application/common/Server/thinkphp/library/think/Error.php new file mode 100644 index 00000000..ea3328ee --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Error.php @@ -0,0 +1,147 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use think\exception\ThrowableError; + +class Error +{ + /** + * 配置参数 + * @var array + */ + protected static $exceptionHandler; + + /** + * 注册异常处理 + * @access public + * @return void + */ + public static function register() + { + error_reporting(E_ALL); + set_error_handler([__CLASS__, 'appError']); + set_exception_handler([__CLASS__, 'appException']); + register_shutdown_function([__CLASS__, 'appShutdown']); + } + + /** + * Exception Handler + * @access public + * @param \Exception|\Throwable $e + */ + public static function appException($e) + { + if (!$e instanceof \Exception) { + $e = new ThrowableError($e); + } + + self::getExceptionHandler()->report($e); + + if (PHP_SAPI == 'cli') { + self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); + } else { + self::getExceptionHandler()->render($e)->send(); + } + } + + /** + * Error Handler + * @access public + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @throws ErrorException + */ + public static function appError($errno, $errstr, $errfile = '', $errline = 0) + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + if (error_reporting() & $errno) { + // 将错误信息托管至 think\exception\ErrorException + throw $exception; + } + + self::getExceptionHandler()->report($exception); + } + + /** + * Shutdown Handler + * @access public + */ + public static function appShutdown() + { + if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { + // 将错误信息托管至think\ErrorException + $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']); + + self::appException($exception); + } + + // 写入日志 + Container::get('log')->save(); + } + + /** + * 确定错误类型是否致命 + * + * @access protected + * @param int $type + * @return bool + */ + protected static function isFatal($type) + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * 设置异常处理类 + * + * @access public + * @param mixed $handle + * @return void + */ + public static function setExceptionHandler($handle) + { + self::$exceptionHandler = $handle; + } + + /** + * Get an instance of the exception handler. + * + * @access public + * @return Handle + */ + public static function getExceptionHandler() + { + static $handle; + + if (!$handle) { + // 异常处理handle + $class = self::$exceptionHandler; + + if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { + $handle = new $class; + } else { + $handle = new Handle; + if ($class instanceof \Closure) { + $handle->setRender($class); + } + } + } + + return $handle; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Exception.php b/Server/application/common/Server/thinkphp/library/think/Exception.php new file mode 100644 index 00000000..414a090a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Exception.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Exception extends \Exception +{ + + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/Facade.php b/Server/application/common/Server/thinkphp/library/think/Facade.php new file mode 100644 index 00000000..ac5ae28b --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Facade.php @@ -0,0 +1,125 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Facade +{ + /** + * 绑定对象 + * @var array + */ + protected static $bind = []; + + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + /** + * 绑定类的静态代理 + * @static + * @access public + * @param string|array $name 类标识 + * @param string $class 类名 + * @return object + */ + public static function bind($name, $class = null) + { + if (__CLASS__ != static::class) { + return self::__callStatic('bind', func_get_args()); + } + + if (is_array($name)) { + self::$bind = array_merge(self::$bind, $name); + } else { + self::$bind[$name] = $class; + } + } + + /** + * 创建Facade实例 + * @static + * @access protected + * @param string $class 类名或标识 + * @param array $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade($class = '', $args = [], $newInstance = false) + { + $class = $class ?: static::class; + + $facadeClass = static::getFacadeClass(); + + if ($facadeClass) { + $class = $facadeClass; + } elseif (isset(self::$bind[$class])) { + $class = self::$bind[$class]; + } + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + return Container::getInstance()->make($class, $args, $newInstance); + } + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 带参数实例化当前Facade类 + * @access public + * @return mixed + */ + public static function instance(...$args) + { + if (__CLASS__ != static::class) { + return self::createFacade('', $args); + } + } + + /** + * 调用类的实例 + * @access public + * @param string $class 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed + */ + public static function make($class, $args = [], $newInstance = false) + { + if (__CLASS__ != static::class) { + return self::__callStatic('make', func_get_args()); + } + + if (true === $args) { + // 总是创建新的实例化对象 + $newInstance = true; + $args = []; + } + + return self::createFacade($class, $args, $newInstance); + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/File.php b/Server/application/common/Server/thinkphp/library/think/File.php new file mode 100644 index 00000000..b24b7770 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/File.php @@ -0,0 +1,496 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use SplFileObject; + +class File extends SplFileObject +{ + /** + * 错误信息 + * @var string + */ + private $error = ''; + + /** + * 当前完整文件名 + * @var string + */ + protected $filename; + + /** + * 上传文件名 + * @var string + */ + protected $saveName; + + /** + * 上传文件命名规则 + * @var string + */ + protected $rule = 'date'; + + /** + * 上传文件验证规则 + * @var array + */ + protected $validate = []; + + /** + * 是否单元测试 + * @var bool + */ + protected $isTest; + + /** + * 上传文件信息 + * @var array + */ + protected $info = []; + + /** + * 文件hash规则 + * @var array + */ + protected $hash = []; + + public function __construct($filename, $mode = 'r') + { + parent::__construct($filename, $mode); + + $this->filename = $this->getRealPath() ?: $this->getPathname(); + } + + /** + * 是否测试 + * @access public + * @param bool $test 是否测试 + * @return $this + */ + public function isTest($test = false) + { + $this->isTest = $test; + + return $this; + } + + /** + * 设置上传信息 + * @access public + * @param array $info 上传文件信息 + * @return $this + */ + public function setUploadInfo($info) + { + $this->info = $info; + + return $this; + } + + /** + * 获取上传文件的信息 + * @access public + * @param string $name + * @return array|string + */ + public function getInfo($name = '') + { + return isset($this->info[$name]) ? $this->info[$name] : $this->info; + } + + /** + * 获取上传文件的文件名 + * @access public + * @return string + */ + public function getSaveName() + { + return $this->saveName; + } + + /** + * 设置上传文件的保存文件名 + * @access public + * @param string $saveName + * @return $this + */ + public function setSaveName($saveName) + { + $this->saveName = $saveName; + + return $this; + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type + * @return string + */ + public function hash($type = 'sha1') + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->filename); + } + + return $this->hash[$type]; + } + + /** + * 检查目录是否可写 + * @access protected + * @param string $path 目录 + * @return boolean + */ + protected function checkPath($path) + { + if (is_dir($path)) { + return true; + } + + if (mkdir($path, 0755, true)) { + return true; + } + + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + return false; + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime() + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->filename); + } + + /** + * 设置文件的命名规则 + * @access public + * @param string $rule 文件命名规则 + * @return $this + */ + public function rule($rule) + { + $this->rule = $rule; + + return $this; + } + + /** + * 设置上传文件的验证规则 + * @access public + * @param array $rule 验证规则 + * @return $this + */ + public function validate($rule = []) + { + $this->validate = $rule; + + return $this; + } + + /** + * 检测是否合法的上传文件 + * @access public + * @return bool + */ + public function isValid() + { + if ($this->isTest) { + return is_file($this->filename); + } + + return is_uploaded_file($this->filename); + } + + /** + * 检测上传文件 + * @access public + * @param array $rule 验证规则 + * @return bool + */ + public function check($rule = []) + { + $rule = $rule ?: $this->validate; + + if ((isset($rule['size']) && !$this->checkSize($rule['size'])) + || (isset($rule['type']) && !$this->checkMime($rule['type'])) + || (isset($rule['ext']) && !$this->checkExt($rule['ext'])) + || !$this->checkImg()) { + return false; + } + + return true; + } + + /** + * 检测上传文件后缀 + * @access public + * @param array|string $ext 允许后缀 + * @return bool + */ + public function checkExt($ext) + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + if (!in_array($extension, $ext)) { + $this->error = 'extensions to upload is not allowed'; + return false; + } + + return true; + } + + /** + * 检测图像文件 + * @access public + * @return bool + */ + public function checkImg() + { + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + /* 对图像文件进行严格检测 */ + if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) { + $this->error = 'illegal image files'; + return false; + } + + return true; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 检测上传文件大小 + * @access public + * @param integer $size 最大大小 + * @return bool + */ + public function checkSize($size) + { + if ($this->getSize() > (int) $size) { + $this->error = 'filesize not match'; + return false; + } + + return true; + } + + /** + * 检测上传文件类型 + * @access public + * @param array|string $mime 允许类型 + * @return bool + */ + public function checkMime($mime) + { + if (is_string($mime)) { + $mime = explode(',', $mime); + } + + if (!in_array(strtolower($this->getMime()), $mime)) { + $this->error = 'mimetype to upload is not allowed'; + return false; + } + + return true; + } + + /** + * 移动文件 + * @access public + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @param bool $autoAppendExt 自动补充扩展名 + * @return false|File false-失败 否则返回File实例 + */ + public function move($path, $savename = true, $replace = true, $autoAppendExt = true) + { + // 文件上传失败,捕获错误代码 + if (!empty($this->info['error'])) { + $this->error($this->info['error']); + return false; + } + + // 检测合法性 + if (!$this->isValid()) { + $this->error = 'upload illegal files'; + return false; + } + + // 验证上传 + if (!$this->check()) { + return false; + } + + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + // 文件保存命名规则 + $saveName = $this->buildSaveName($savename, $autoAppendExt); + $filename = $path . $saveName; + + // 检测目录 + if (false === $this->checkPath(dirname($filename))) { + return false; + } + + /* 不覆盖同名文件 */ + if (!$replace && is_file($filename)) { + $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; + return false; + } + + /* 移动文件 */ + if ($this->isTest) { + rename($this->filename, $filename); + } elseif (!move_uploaded_file($this->filename, $filename)) { + $this->error = 'upload write error'; + return false; + } + + // 返回 File对象实例 + $file = new self($filename); + $file->setSaveName($saveName); + $file->setUploadInfo($this->info); + + return $file; + } + + /** + * 获取保存文件名 + * @access protected + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param bool $autoAppendExt 自动补充扩展名 + * @return string + */ + protected function buildSaveName($savename, $autoAppendExt = true) + { + if (true === $savename) { + // 自动生成文件名 + $savename = $this->autoBuildName(); + } elseif ('' === $savename || false === $savename) { + // 保留原文件名 + $savename = $this->getInfo('name'); + } + + if ($autoAppendExt && false === strpos($savename, '.')) { + $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); + } + + return $savename; + } + + /** + * 自动生成文件名 + * @access protected + * @return string + */ + protected function autoBuildName() + { + if ($this->rule instanceof \Closure) { + $savename = call_user_func_array($this->rule, [$this]); + } else { + switch ($this->rule) { + case 'date': + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); + } + } + } + + return $savename; + } + + /** + * 获取错误代码信息 + * @access private + * @param int $errorNo 错误号 + */ + private function error($errorNo) + { + switch ($errorNo) { + case 1: + case 2: + $this->error = 'upload File size exceeds the maximum value'; + break; + case 3: + $this->error = 'only the portion of file is uploaded'; + break; + case 4: + $this->error = 'no file to uploaded'; + break; + case 6: + $this->error = 'upload temp dir not found'; + break; + case 7: + $this->error = 'file write error'; + break; + default: + $this->error = 'unknown upload error'; + } + } + + /** + * 获取错误信息(支持多语言) + * @access public + * @return string + */ + public function getError() + { + $lang = Container::get('lang'); + + if (is_array($this->error)) { + list($msg, $vars) = $this->error; + } else { + $msg = $this->error; + $vars = []; + } + + return $lang->has($msg) ? $lang->get($msg, $vars) : $msg; + } + + public function __call($method, $args) + { + return $this->hash($method); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Hook.php b/Server/application/common/Server/thinkphp/library/think/Hook.php new file mode 100644 index 00000000..1d011410 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Hook.php @@ -0,0 +1,220 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Hook +{ + /** + * 钩子行为定义 + * @var array + */ + private $tags = []; + + /** + * 绑定行为列表 + * @var array + */ + protected $bind = []; + + /** + * 入口方法名称 + * @var string + */ + private static $portal = 'run'; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 指定入口方法名称 + * @access public + * @param string $name 方法名 + * @return $this + */ + public function portal($name) + { + self::$portal = $name; + return $this; + } + + /** + * 指定行为标识 便于调用 + * @access public + * @param string|array $name 行为标识 + * @param mixed $behavior 行为 + * @return $this + */ + public function alias($name, $behavior = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $behavior; + } + + return $this; + } + + /** + * 动态添加行为扩展到某个标签 + * @access public + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 + * @return void + */ + public function add($tag, $behavior, $first = false) + { + isset($this->tags[$tag]) || $this->tags[$tag] = []; + + if (is_array($behavior) && !is_callable($behavior)) { + if (!array_key_exists('_overlay', $behavior)) { + $this->tags[$tag] = array_merge($this->tags[$tag], $behavior); + } else { + unset($behavior['_overlay']); + $this->tags[$tag] = $behavior; + } + } elseif ($first) { + array_unshift($this->tags[$tag], $behavior); + } else { + $this->tags[$tag][] = $behavior; + } + } + + /** + * 批量导入插件 + * @access public + * @param array $tags 插件信息 + * @param bool $recursive 是否递归合并 + * @return void + */ + public function import(array $tags, $recursive = true) + { + if ($recursive) { + foreach ($tags as $tag => $behavior) { + $this->add($tag, $behavior); + } + } else { + $this->tags = $tags + $this->tags; + } + } + + /** + * 获取插件信息 + * @access public + * @param string $tag 插件位置 留空获取全部 + * @return array + */ + public function get($tag = '') + { + if (empty($tag)) { + //获取全部的插件信息 + return $this->tags; + } + + return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : []; + } + + /** + * 监听标签的行为 + * @access public + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public function listen($tag, $params = null, $once = false) + { + $results = []; + $tags = $this->get($tag); + + foreach ($tags as $key => $name) { + $results[$key] = $this->execTag($name, $tag, $params); + + if (false === $results[$key] || (!is_null($results[$key]) && $once)) { + break; + } + } + + return $once ? end($results) : $results; + } + + /** + * 执行行为 + * @access public + * @param mixed $class 行为 + * @param mixed $params 参数 + * @return mixed + */ + public function exec($class, $params = null) + { + if ($class instanceof \Closure || is_array($class)) { + $method = $class; + } else { + if (isset($this->bind[$class])) { + $class = $this->bind[$class]; + } + $method = [$class, self::$portal]; + } + + return $this->app->invoke($method, [$params]); + } + + /** + * 执行某个标签的行为 + * @access protected + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 参数 + * @return mixed + */ + protected function execTag($class, $tag = '', $params = null) + { + $method = Loader::parseName($tag, 1, false); + + if ($class instanceof \Closure) { + $call = $class; + $class = 'Closure'; + } elseif (is_array($class) || strpos($class, '::')) { + $call = $class; + } else { + $obj = Container::get($class); + + if (!is_callable([$obj, $method])) { + $method = self::$portal; + } + + $call = [$class, $method]; + $class = $class . '->' . $method; + } + + $result = $this->app->invoke($call, [$params]); + + return $result; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Lang.php b/Server/application/common/Server/thinkphp/library/think/Lang.php new file mode 100644 index 00000000..ed36dd8c --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Lang.php @@ -0,0 +1,290 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Lang +{ + /** + * 多语言信息 + * @var array + */ + private $lang = []; + + /** + * 当前语言 + * @var string + */ + private $range = 'zh-cn'; + + /** + * 多语言自动侦测变量名 + * @var string + */ + protected $langDetectVar = 'lang'; + + /** + * 多语言cookie变量 + * @var string + */ + protected $langCookieVar = 'think_var'; + + /** + * 允许的多语言列表 + * @var array + */ + protected $allowLangList = []; + + /** + * Accept-Language转义为对应语言包名称 系统默认配置 + * @var string + */ + protected $acceptLanguage = [ + 'zh-hans-cn' => 'zh-cn', + ]; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + // 设定当前的语言 + public function range($range = '') + { + if ('' == $range) { + return $this->range; + } else { + $this->range = $range; + } + } + + /** + * 设置语言定义(不区分大小写) + * @access public + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 语言作用域 + * @return mixed + */ + public function set($name, $value = null, $range = '') + { + $range = $range ?: $this->range; + // 批量定义 + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + + if (is_array($name)) { + return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range]; + } + + return $this->lang[$range][strtolower($name)] = $value; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param string|array $file 语言文件 + * @param string $range 语言作用域 + * @return array + */ + public function load($file, $range = '') + { + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + + // 批量定义 + if (is_string($file)) { + $file = [$file]; + } + + $lang = []; + + foreach ($file as $_file) { + if (is_file($_file)) { + // 记录加载信息 + $this->app->log('[ LANG ] ' . $_file); + $_lang = include $_file; + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } + } + } + + if (!empty($lang)) { + $this->lang[$range] = $lang + $this->lang[$range]; + } + + return $this->lang[$range]; + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return bool + */ + public function has($name, $range = '') + { + $range = $range ?: $this->range; + + return isset($this->lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public function get($name = null, $vars = [], $range = '') + { + $range = $range ?: $this->range; + + // 空参数返回所有定义 + if (is_null($name)) { + return $this->lang[$range]; + } + + $key = strtolower($name); + $value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name; + + // 变量解析 + if (!empty($vars) && is_array($vars)) { + /** + * Notes: + * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 + * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 + */ + if (key($vars) === 0) { + // 数字索引解析 + array_unshift($vars, $value); + $value = call_user_func_array('sprintf', $vars); + } else { + // 关联索引解析 + $replace = array_keys($vars); + foreach ($replace as &$v) { + $v = "{:{$v}}"; + } + $value = str_replace($replace, $vars, $value); + } + } + + return $value; + } + + /** + * 自动侦测设置获取语言选择 + * @access public + * @return string + */ + public function detect() + { + // 自动侦测设置获取语言选择 + $langSet = ''; + + if (isset($_GET[$this->langDetectVar])) { + // url中设置了语言变量 + $langSet = strtolower($_GET[$this->langDetectVar]); + } elseif (isset($_COOKIE[$this->langCookieVar])) { + // Cookie中设置了语言变量 + $langSet = strtolower($_COOKIE[$this->langCookieVar]); + } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + // 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = strtolower($matches[1]); + if (isset($this->acceptLanguage[$langSet])) { + $langSet = $this->acceptLanguage[$langSet]; + } + } + + if (preg_match('/^([a-z\d\-]+)/i', $langSet, $matches)) { + $langSet = strtolower($matches[1]); + } else { + $langSet = $this->range; + } + + if (empty($this->allowLangList) || in_array($langSet, $this->allowLangList)) { + // 合法的语言 + $this->range = $langSet ?: $this->range; + } + + return $this->range; + } + + /** + * 设置当前语言到Cookie + * @access public + * @param string $lang 语言 + * @return void + */ + public function saveToCookie($lang = null) + { + $range = $lang ?: $this->range; + + $_COOKIE[$this->langCookieVar] = $range; + } + + /** + * 设置语言自动侦测的变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public function setLangDetectVar($var) + { + $this->langDetectVar = $var; + } + + /** + * 设置语言的cookie保存变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public function setLangCookieVar($var) + { + $this->langCookieVar = $var; + } + + /** + * 设置允许的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public function setAllowLangList(array $list) + { + $this->allowLangList = $list; + } + + /** + * 设置转义的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public function setAcceptLanguage(array $list) + { + $this->acceptLanguage = array_merge($this->acceptLanguage, $list); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Loader.php b/Server/application/common/Server/thinkphp/library/think/Loader.php new file mode 100644 index 00000000..d807db64 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Loader.php @@ -0,0 +1,417 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Loader +{ + /** + * 类名映射信息 + * @var array + */ + protected static $classMap = []; + + /** + * 类库别名 + * @var array + */ + protected static $classAlias = []; + + /** + * PSR-4 + * @var array + */ + private static $prefixLengthsPsr4 = []; + private static $prefixDirsPsr4 = []; + private static $fallbackDirsPsr4 = []; + + /** + * PSR-0 + * @var array + */ + private static $prefixesPsr0 = []; + private static $fallbackDirsPsr0 = []; + + /** + * 需要加载的文件 + * @var array + */ + private static $files = []; + + /** + * Composer安装路径 + * @var string + */ + private static $composerPath; + + // 获取应用根目录 + public static function getRootPath() + { + if ('cli' == PHP_SAPI) { + $scriptName = realpath($_SERVER['argv'][0]); + } else { + $scriptName = $_SERVER['SCRIPT_FILENAME']; + } + + $path = realpath(dirname($scriptName)); + + if (!is_file($path . DIRECTORY_SEPARATOR . 'think')) { + $path = dirname($path); + } + + return $path . DIRECTORY_SEPARATOR; + } + + // 注册自动加载机制 + public static function register($autoload = '') + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + $rootPath = self::getRootPath(); + + self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; + + // Composer自动加载支持 + if (is_dir(self::$composerPath)) { + if (is_file(self::$composerPath . 'autoload_static.php')) { + require self::$composerPath . 'autoload_static.php'; + + $declaredClass = get_declared_classes(); + $composerClass = array_pop($declaredClass); + + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { + if (property_exists($composerClass, $attr)) { + self::${$attr} = $composerClass::${$attr}; + } + } + } else { + self::registerComposerLoader(self::$composerPath); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => __DIR__, + 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', + ]); + + // 加载类库映射文件 + if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) { + self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')); + } + + // 自动加载extend目录 + self::addAutoLoadDir($rootPath . 'extend'); + } + + // 自动加载 + public static function autoload($class) + { + if (isset(self::$classAlias[$class])) { + return class_alias(self::$classAlias[$class], $class); + } + + if ($file = self::findFile($class)) { + + // Win环境严格区分大小写 + if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) { + return false; + } + + __include_file($file); + return true; + } + } + + /** + * 查找文件 + * @access private + * @param string $class + * @return string|false + */ + private static function findFile($class) + { + if (!empty(self::$classMap[$class])) { + // 类库映射 + return self::$classMap[$class]; + } + + // 查找 PSR-4 + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + + $first = $class[0]; + if (isset(self::$prefixLengthsPsr4[$first])) { + foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach (self::$prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // 查找 PSR-4 fallback dirs + foreach (self::$fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // 查找 PSR-0 + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; + } + + if (isset(self::$prefixesPsr0[$first])) { + foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // 查找 PSR-0 fallback dirs + foreach (self::$fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + return self::$classMap[$class] = false; + } + + // 注册classmap + public static function addClassMap($class, $map = '') + { + if (is_array($class)) { + self::$classMap = array_merge(self::$classMap, $class); + } else { + self::$classMap[$class] = $map; + } + } + + // 注册命名空间 + public static function addNamespace($namespace, $path = '') + { + if (is_array($namespace)) { + foreach ($namespace as $prefix => $paths) { + self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true); + } + } else { + self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true); + } + } + + // 添加Ps0空间 + private static function addPsr0($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + self::$fallbackDirsPsr0 = array_merge( + (array) $paths, + self::$fallbackDirsPsr0 + ); + } else { + self::$fallbackDirsPsr0 = array_merge( + self::$fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + + if ($prepend) { + self::$prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + self::$prefixesPsr0[$first][$prefix] + ); + } else { + self::$prefixesPsr0[$first][$prefix] = array_merge( + self::$prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + // 添加Psr4空间 + private static function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + self::$fallbackDirsPsr4 = array_merge( + (array) $paths, + self::$fallbackDirsPsr4 + ); + } else { + self::$fallbackDirsPsr4 = array_merge( + self::$fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + + self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + self::$prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + self::$prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + self::$prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + self::$prefixDirsPsr4[$prefix] = array_merge( + self::$prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + // 注册自动加载类库目录 + public static function addAutoLoadDir($path) + { + self::$fallbackDirsPsr4[] = $path; + } + + // 注册类别名 + public static function addClassAlias($alias, $class = null) + { + if (is_array($alias)) { + self::$classAlias = array_merge(self::$classAlias, $alias); + } else { + self::$classAlias[$alias] = $class; + } + } + + // 注册composer自动加载 + public static function registerComposerLoader($composerPath) + { + if (is_file($composerPath . 'autoload_namespaces.php')) { + $map = require $composerPath . 'autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + self::addPsr0($namespace, $path); + } + } + + if (is_file($composerPath . 'autoload_psr4.php')) { + $map = require $composerPath . 'autoload_psr4.php'; + foreach ($map as $namespace => $path) { + self::addPsr4($namespace, $path); + } + } + + if (is_file($composerPath . 'autoload_classmap.php')) { + $classMap = require $composerPath . 'autoload_classmap.php'; + if ($classMap) { + self::addClassMap($classMap); + } + } + + if (is_file($composerPath . 'autoload_files.php')) { + self::$files = require $composerPath . 'autoload_files.php'; + } + } + + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + foreach (self::$files as $fileIdentifier => $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + __require_file($file); + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } + } + } + + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @access public + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + public static function parseName($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + return $ucfirst ? ucfirst($name) : lcfirst($name); + } + + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + + /** + * 创建工厂对象实例 + * @access public + * @param string $name 工厂类名 + * @param string $namespace 默认命名空间 + * @return mixed + */ + public static function factory($name, $namespace = '', ...$args) + { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); + + if (class_exists($class)) { + return Container::getInstance()->invokeClass($class, $args); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } +} + +/** + * 作用范围隔离 + * + * @param $file + * @return mixed + */ +function __include_file($file) +{ + return include $file; +} + +function __require_file($file) +{ + return require $file; +} diff --git a/Server/application/common/Server/thinkphp/library/think/Log.php b/Server/application/common/Server/thinkphp/library/think/Log.php new file mode 100644 index 00000000..8902e976 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Log.php @@ -0,0 +1,389 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Log implements LoggerInterface +{ + const EMERGENCY = 'emergency'; + const ALERT = 'alert'; + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const NOTICE = 'notice'; + const INFO = 'info'; + const DEBUG = 'debug'; + const SQL = 'sql'; + + /** + * 日志信息 + * @var array + */ + protected $log = []; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 日志写入驱动 + * @var object + */ + protected $driver; + + /** + * 日志授权key + * @var string + */ + protected $key; + + /** + * 是否允许日志写入 + * @var bool + */ + protected $allowWrite = true; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + public static function __make(App $app, Config $config) + { + return (new static($app))->init($config->pull('log')); + } + + /** + * 日志初始化 + * @access public + * @param array $config + * @return $this + */ + public function init($config = []) + { + $type = isset($config['type']) ? $config['type'] : 'File'; + + $this->config = $config; + + unset($config['type']); + + if (!empty($config['close'])) { + $this->allowWrite = false; + } + + $this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config); + + return $this; + } + + /** + * 获取日志信息 + * @access public + * @param string $type 信息类型 + * @return array + */ + public function getLog($type = '') + { + return $type ? $this->log[$type] : $this->log; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function record($msg, $type = 'info', array $context = []) + { + if (!$this->allowWrite) { + return; + } + + if (is_string($msg) && !empty($context)) { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + $msg = strtr($msg, $replace); + } + + if (PHP_SAPI == 'cli') { + if (empty($this->config['level']) || in_array($type, $this->config['level'])) { + // 命令行日志实时写入 + $this->write($msg, $type, true); + } + } else { + $this->log[$type][] = $msg; + } + + return $this; + } + + /** + * 清空日志信息 + * @access public + * @return $this + */ + public function clear() + { + $this->log = []; + + return $this; + } + + /** + * 当前日志记录的授权key + * @access public + * @param string $key 授权key + * @return $this + */ + public function key($key) + { + $this->key = $key; + + return $this; + } + + /** + * 检查日志写入权限 + * @access public + * @param array $config 当前日志配置参数 + * @return bool + */ + public function check($config) + { + if ($this->key && !empty($config['allow_key']) && !in_array($this->key, $config['allow_key'])) { + return false; + } + + return true; + } + + /** + * 关闭本次请求日志写入 + * @access public + * @return $this + */ + public function close() + { + $this->allowWrite = false; + $this->log = []; + + return $this; + } + + /** + * 保存调试信息 + * @access public + * @return bool + */ + public function save() + { + if (empty($this->log) || !$this->allowWrite) { + return true; + } + + if (!$this->check($this->config)) { + // 检测日志写入权限 + return false; + } + + $log = []; + + foreach ($this->log as $level => $info) { + if (!$this->app->isDebug() && 'debug' == $level) { + continue; + } + + if (empty($this->config['level']) || in_array($level, $this->config['level'])) { + $log[$level] = $info; + + $this->app['hook']->listen('log_level', [$level, $info]); + } + } + + $result = $this->driver->save($log, true); + + if ($result) { + $this->log = []; + } + + return $result; + } + + /** + * 实时写入日志信息 并支持行为 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param bool $force 是否强制写入 + * @return bool + */ + public function write($msg, $type = 'info', $force = false) + { + // 封装日志信息 + if (empty($this->config['level'])) { + $force = true; + } + + if (true === $force || in_array($type, $this->config['level'])) { + $log[$type][] = $msg; + } else { + return false; + } + + // 监听log_write + $this->app['hook']->listen('log_write', $log); + + // 写入日志 + return $this->driver->save($log, false); + } + + /** + * 记录日志信息 + * @access public + * @param string $level 日志级别 + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function log($level, $message, array $context = []) + { + $this->record($message, $level, $context); + } + + /** + * 记录emergency信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function emergency($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录警报信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function alert($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录紧急情况 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function critical($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录错误信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function error($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录warning信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function warning($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录notice信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function notice($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录一般信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function info($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录调试信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function debug($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录sql信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function sql($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Middleware.php b/Server/application/common/Server/thinkphp/library/think/Middleware.php new file mode 100644 index 00000000..d3f43606 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Middleware.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use InvalidArgumentException; +use LogicException; +use think\exception\HttpResponseException; + +class Middleware +{ + protected $queue = []; + protected $app; + protected $config = [ + 'default_namespace' => 'app\\http\\middleware\\', + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, $config); + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('middleware')); + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 导入中间件 + * @access public + * @param array $middlewares + * @param string $type 中间件类型 + */ + public function import(array $middlewares = [], $type = 'route') + { + foreach ($middlewares as $middleware) { + $this->add($middleware, $type); + } + } + + /** + * 注册中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + */ + public function add($middleware, $type = 'route') + { + if (is_null($middleware)) { + return; + } + + $middleware = $this->buildMiddleware($middleware, $type); + + if ($middleware) { + $this->queue[$type][] = $middleware; + } + } + + /** + * 注册控制器中间件 + * @access public + * @param mixed $middleware + */ + public function controller($middleware) + { + return $this->add($middleware, 'controller'); + } + + /** + * 移除中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + */ + public function unshift($middleware, $type = 'route') + { + if (is_null($middleware)) { + return; + } + + $middleware = $this->buildMiddleware($middleware, $type); + + if ($middleware) { + array_unshift($this->queue[$type], $middleware); + } + } + + /** + * 获取注册的中间件 + * @access public + * @param string $type 中间件类型 + */ + public function all($type = 'route') + { + return $this->queue[$type] ?: []; + } + + /** + * 清除中间件 + * @access public + */ + public function clear() + { + $this->queue = []; + } + + /** + * 中间件调度 + * @access public + * @param Request $request + * @param string $type 中间件类型 + */ + public function dispatch(Request $request, $type = 'route') + { + return call_user_func($this->resolve($type), $request); + } + + /** + * 解析中间件 + * @access protected + * @param mixed $middleware + * @param string $type 中间件类型 + */ + protected function buildMiddleware($middleware, $type = 'route') + { + if (is_array($middleware)) { + list($middleware, $param) = $middleware; + } + + if ($middleware instanceof \Closure) { + return [$middleware, isset($param) ? $param : null]; + } + + if (!is_string($middleware)) { + throw new InvalidArgumentException('The middleware is invalid'); + } + + if (false === strpos($middleware, '\\')) { + if (isset($this->config[$middleware])) { + $middleware = $this->config[$middleware]; + } else { + $middleware = $this->config['default_namespace'] . $middleware; + } + } + + if (is_array($middleware)) { + return $this->import($middleware, $type); + } + + if (strpos($middleware, ':')) { + list($middleware, $param) = explode(':', $middleware, 2); + } + + return [[$this->app->make($middleware), 'handle'], isset($param) ? $param : null]; + } + + protected function resolve($type = 'route') + { + return function (Request $request) use ($type) { + + $middleware = array_shift($this->queue[$type]); + + if (null === $middleware) { + throw new InvalidArgumentException('The queue was exhausted, with no response returned'); + } + + list($call, $param) = $middleware; + + try { + $response = call_user_func_array($call, [$request, $this->resolve($type), $param]); + } catch (HttpResponseException $exception) { + $response = $exception->getResponse(); + } + + if (!$response instanceof Response) { + throw new LogicException('The middleware must return Response instance'); + } + + return $response; + }; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Model.php b/Server/application/common/Server/thinkphp/library/think/Model.php new file mode 100644 index 00000000..50f2ca14 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Model.php @@ -0,0 +1,1125 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use InvalidArgumentException; +use think\db\Query; + +/** + * Class Model + * @package think + * @mixin Query + * @method $this scope(string|array $scope) static 查询范围 + * @method $this where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method $this whereRaw(string $where, array $bind = [], string $logic = 'AND') static 表达式查询 + * @method $this whereExp(string $field, string $condition, array $bind = [], string $logic = 'AND') static 字段表达式查询 + * @method $this when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询 + * @method $this join(mixed $join, mixed $condition = null, string $type = 'INNER', array $bind = []) static JOIN查询 + * @method $this view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method $this with(mixed $with, callable $callback = null) static 关联预载入 + * @method $this count(string $field = '*') static Count统计查询 + * @method $this min(string $field, bool $force = true) static Min统计查询 + * @method $this max(string $field, bool $force = true) static Max统计查询 + * @method $this sum(string $field) static SUM统计查询 + * @method $this avg(string $field) static Avg统计查询 + * @method $this field(mixed $field, boolean $except = false, string $tableName = '', string $prefix = '', string $alias = '') static 指定查询字段 + * @method $this fieldRaw(string $field) static 指定查询字段 + * @method $this union(mixed $union, boolean $all = false) static UNION查询 + * @method $this limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method $this order(mixed $field, string $order = null) static 查询ORDER + * @method $this orderRaw(string $field, array $bind = []) static 查询ORDER + * @method $this cache(mixed $key = null, integer|\DateTime $expire = null, string $tag = null) static 设置查询缓存 + * @method mixed value(string $field, mixed $default = null) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method $this find(mixed $data = null) static 查询单个记录 + * @method $this findOrFail(mixed $data = null) 查询单个记录 + * @method Collection|$this[] select(mixed $data = null) static 查询多个记录 + * @method $this get(mixed $data = null, mixed $with = [], bool $cache = false, bool $failException = false) static 查询单个记录 支持关联预载入 + * @method $this getOrFail(mixed $data = null, mixed $with = [], bool $cache = false) static 查询单个记录 不存在则抛出异常 + * @method $this findOrEmpty(mixed $data = null) static 查询单个记录 不存在则返回空模型 + * @method Collection|$this[] all(mixed $data = null, mixed $with = [], bool $cache = false) static 查询多个记录 支持关联预载入 + * @method $this withAttr(array $name, \Closure $closure = null) static 动态定义获取器 + * @method $this withJoin(string|array $with, string $joinType = '') static + * @method $this withCount(string|array $relation, bool $subQuery = true) static 关联统计 + * @method $this withSum(string|array $relation, string $field, bool $subQuery = true) static 关联SUM统计 + * @method $this withMax(string|array $relation, string $field, bool $subQuery = true) static 关联MAX统计 + * @method $this withMin(string|array $relation, string $field, bool $subQuery = true) static 关联Min统计 + * @method $this withAvg(string|array $relation, string $field, bool $subQuery = true) static 关联Avg统计 + * @method Paginator|$this paginate(int|array $listRows = null, int|bool $simple = false, array $config = []) static 分页 + */ +abstract class Model implements \JsonSerializable, \ArrayAccess +{ + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; + + /** + * 是否存在数据 + * @var bool + */ + private $exists = false; + + /** + * 是否Replace + * @var bool + */ + private $replace = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置信息 + * @var array|string + */ + protected $connection = []; + + /** + * 数据库查询对象类名 + * @var string + */ + protected $query; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 写入自动完成定义 + * @var array + */ + protected $auto = []; + + /** + * 新增自动完成定义 + * @var array + */ + protected $insert = []; + + /** + * 更新自动完成定义 + * @var array + */ + protected $update = []; + + /** + * 初始化过的模型. + * @var array + */ + protected static $initialized = []; + + /** + * 是否从主库读取(主从分布式有效) + * @var array + */ + protected static $readMaster; + + /** + * 查询对象实例 + * @var Query + */ + protected $queryInstance; + + /** + * 错误信息 + * @var mixed + */ + protected $error; + + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + + /** + * 全局查询范围 + * @var array + */ + protected $globalScope = []; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + if (is_object($data)) { + $this->data = get_object_vars($data); + } else { + $this->data = $data; + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + $config = Db::getConfig(); + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', static::class); + $this->name = basename($name); + if (Container::get('config')->get('class_suffix')) { + $suffix = basename(dirname($name)); + $this->name = substr($this->name, 0, -strlen($suffix)); + } + } + + if (is_null($this->autoWriteTimestamp)) { + // 自动写入时间戳 + $this->autoWriteTimestamp = $config['auto_timestamp']; + } + + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $config['datetime_format']; + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $config['resultset_type']; + } + + if (!empty($this->connection) && is_array($this->connection)) { + // 设置模型的数据库连接 + $this->connection = array_merge($config, $this->connection); + } + + if ($this->observerClass) { + // 注册模型观察者 + static::observe($this->observerClass); + } + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 获取当前模型名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否从主库读取数据(主从分布有效) + * @access public + * @param bool $all 是否所有模型有效 + * @return $this + */ + public function readMaster($all = false) + { + $model = $all ? '*' : static::class; + + static::$readMaster[$model] = true; + + return $this; + } + + /** + * 创建新的模型实例 + * @access public + * @param array|object $data 数据 + * @param bool $isUpdate 是否为更新 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance($data = [], $isUpdate = false, $where = null) + { + return (new static($data))->isUpdate($isUpdate, $where); + } + + /** + * 创建模型的查询对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + // 设置当前模型 确保查询返回模型对象 + $query = Db::connect($this->connection, false, $this->query); + $query->model($this) + ->name($this->name) + ->json($this->json, $this->jsonAssoc) + ->setJsonFieldType($this->jsonType); + + if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) { + $query->master(true); + } + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->table($this->table); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param Query $query 查询对象实例 + * @return $this + */ + public function setQuery($query) + { + $this->queryInstance = $query; + return $this; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool|array $useBaseQuery 是否调用全局查询范围(或者指定查询范围名称) + * @return Query + */ + public function db($useBaseQuery = true) + { + if ($this->queryInstance) { + return $this->queryInstance; + } + + $query = $this->buildQuery(); + + // 软删除 + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { + $this->withNoTrashed($query); + } + + // 全局作用域 + if (true === $useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } + + $globalScope = is_array($useBaseQuery) && $useBaseQuery ? $useBaseQuery : $this->globalScope; + + if ($globalScope && false !== $useBaseQuery) { + $query->scope($globalScope); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access protected + * @return void + */ + protected function initialize() + { + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + {} + + /** + * 数据自动完成 + * @access protected + * @param array $auto 要自动更新的字段列表 + * @return void + */ + protected function autoCompleteData($auto = []) + { + foreach ($auto as $field => $value) { + if (is_integer($field)) { + $field = $value; + $value = null; + } + + if (!isset($this->data[$field])) { + $default = null; + } else { + $default = $this->data[$field]; + } + + $this->setAttr($field, !is_null($value) ? $value : $default); + } + } + + /** + * 更新是否强制写入数据 而不做比较 + * @access public + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + /** + * 判断force + * @access public + * @return bool + */ + public function isForce() + { + return $this->force; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return $this + */ + public function exists($exists) + { + $this->exists = $exists; + return $this; + } + + /** + * 判断数据是否存在数据库 + * @access public + * @return bool + */ + public function isExists() + { + return $this->exists; + } + + /** + * 判断模型是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->data); + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + if (!$this->checkBeforeSave($data, $where)) { + return false; + } + + $result = $this->exists ? $this->updateData($where) : $this->insertData($sequence); + + if (false === $result) { + return false; + } + + // 写入回调 + $this->trigger('after_write'); + + // 重新记录原始数据 + $this->origin = $this->data; + $this->set = []; + + return true; + } + + /** + * 写入之前检查数据 + * @access protected + * @param array $data 数据 + * @param array $where 保存条件 + * @return bool + */ + protected function checkBeforeSave($data, $where) + { + if (!empty($data)) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + + if (!empty($where)) { + $this->exists = true; + $this->updateWhere = $where; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write')) { + return false; + } + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @param array $append 自动完成的字段列表 + * @return array + */ + protected function checkAllowFields(array $append = []) + { + // 检测字段 + if (empty($this->field) || true === $this->field) { + $query = $this->db(false); + $table = $this->table ?: $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + + $field = $this->field; + } else { + $field = array_merge($this->field, $append); + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + + return $field; + } + + /** + * 更新写入数据 + * @access protected + * @param mixed $where 更新条件 + * @return bool + */ + protected function updateData($where) + { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return true; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->update)); + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $pk = $this->getPk(); + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[] = [$key, '=', $data[$key]]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + foreach ((array) $this->relationWrite as $name => $val) { + if (is_array($val)) { + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + } + + // 模型更新 + $db = $this->db(false); + $db->startTrans(); + + try { + $db->where($where) + ->strict(false) + ->field($allowFields) + ->update($data); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + $db->commit(); + + // 更新回调 + $this->trigger('after_update'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增序列名 + * @return bool + */ + protected function insertData($sequence) + { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 时间戳自动写入 + $this->checkTimeStampWrite(); + + if (false === $this->trigger('before_insert')) { + return false; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert)); + + $db = $this->db(false); + $db->startTrans(); + + try { + $result = $db->strict(false) + ->field($allowFields) + ->insert($this->data, $this->replace, false, $sequence); + + // 获取自动增长主键 + if ($result && $insertId = $db->getLastInsID($sequence)) { + $pk = $this->getPk(); + + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + + $db->commit(); + + // 标记为更新 + $this->exists = true; + + // 新增回调 + $this->trigger('after_insert'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return bool + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + $result = $this->db(false) + ->where($where) + ->setInc($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] += $step; + } + + // 更新回调 + $this->trigger('after_update'); + + return true; + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return bool + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + $result = $this->db(false) + ->where($where) + ->setDec($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] -= $step; + } + + // 更新回调 + $this->trigger('after_update'); + + return true; + } + + /** + * 获取当前的更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + $where = []; + if (is_string($pk) && isset($this->data[$pk])) { + $where[] = [$pk, '=', $this->data[$pk]]; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($this->data[$field])) { + $where[] = [$field, '=', $this->data[$field]]; + } + } + } + + if (empty($where)) { + $where = empty($this->updateWhere) ? null : $this->updateWhere; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + $db = $this->db(false); + $db->startTrans(); + + try { + $pk = $this->getPk(); + + if (is_string($pk) && $replace) { + $auto = true; + } + + $result = []; + + foreach ($dataSet as $key => $data) { + if ($this->exists || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field, $this->replace); + } + } + + $db->commit(); + + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 是否为更新数据 + * @access public + * @param mixed $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + if (is_bool($update)) { + $this->exists = $update; + + if (!empty($where)) { + $this->updateWhere = $where; + } + } else { + $this->exists = true; + $this->updateWhere = $update; + } + + return $this; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete() + { + if (!$this->exists || false === $this->trigger('before_delete')) { + return false; + } + + // 读取更新条件 + $where = $this->getWhere(); + + $db = $this->db(false); + $db->startTrans(); + + try { + // 删除当前模型数据 + $db->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $db->commit(); + + $this->trigger('after_delete'); + + $this->exists = false; + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 设置自动完成的字段( 规则通过修改器定义) + * @access public + * @param array $fields 需要自动完成的字段 + * @return $this + */ + public function auto($fields) + { + $this->auto = $fields; + + return $this; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @param bool $replace 使用Replace + * @return static + */ + public static function create($data = [], $field = null, $replace = false) + { + $model = new static(); + + if (!empty($field)) { + $model->allowField($field); + } + + $model->isUpdate(false)->replace($replace)->save($data, []); + + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return static + */ + public static function update($data = [], $where = [], $field = null) + { + $model = new static(); + + if (!empty($field)) { + $model->allowField($field); + } + + $model->isUpdate(true)->save($data, $where); + + return $model; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @return bool + */ + public static function destroy($data) + { + if (empty($data) && 0 !== $data) { + return false; + } + + $model = new static(); + + $query = $model->db(); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($query); + $data = null; + } + + $resultSet = $query->select($data); + + if ($resultSet) { + foreach ($resultSet as $data) { + $data->delete(); + } + } + + return true; + } + + /** + * 获取错误信息 + * @access public + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + public function __debugInfo() + { + return [ + 'data' => $this->data, + 'relation' => $this->relation, + ]; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + try { + return !is_null($this->getAttr($name)); + } catch (InvalidArgumentException $e) { + return false; + } + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->data[$name], $this->relation[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 设置是否使用全局查询范围 + * @access public + * @param bool|array $use 是否启用全局查询范围(或者用数组指定查询范围名称) + * @return Query + */ + public static function useGlobalScope($use) + { + $model = new static(); + + return $model->db($use); + } + + public function __call($method, $args) + { + if ('withattr' == strtolower($method)) { + return call_user_func_array([$this, 'withAttribute'], $args); + } + + return call_user_func_array([$this->db(), $method], $args); + } + + public static function __callStatic($method, $args) + { + $model = new static(); + + return call_user_func_array([$model->db(), $method], $args); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Paginator.php b/Server/application/common/Server/thinkphp/library/think/Paginator.php new file mode 100644 index 00000000..bbe63e2e --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Paginator.php @@ -0,0 +1,445 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; + +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 是否简洁模式 + * @var bool + */ + protected $simple = false; + + /** + * 数据集 + * @var Collection + */ + protected $items; + + /** + * 当前页 + * @var integer + */ + protected $currentPage; + + /** + * 最后一页 + * @var integer + */ + protected $lastPage; + + /** + * 数据总数 + * @var integer|null + */ + protected $total; + + /** + * 每页数量 + * @var integer + */ + protected $listRows; + + /** + * 是否有下一页 + * @var bool + */ + protected $hasMore; + + /** + * 分页配置 + * @var array + */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + $items = $items->slice(0, $this->listRows); + } else { + $this->total = $total; + $this->lastPage = (int) ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + $this->items = $items; + } + + /** + * @access public + * @param $items + * @param $listRows + * @param null $currentPage + * @param null $total + * @param bool $simple + * @param array $options + * @return Paginator + */ + public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + return new static($items, $listRows, $currentPage, $total, $simple, $options); + } + + protected function setCurrentPage($currentPage) + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @access protected + * @param $page + * @return string + */ + protected function url($page) + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', $page, $this->options['path']); + } + + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + + $url = $path; + if (!empty($parameters)) { + $url .= '?' . http_build_query($parameters, null, '&'); + } + + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @access public + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage($varPage = 'page', $default = 1) + { + $page = Container::get('request')->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { + return $page; + } + + return $default; + } + + /** + * 自动获取当前的path + * @access public + * @return string + */ + public static function getCurrentPath() + { + return Container::get('request')->baseUrl(); + } + + public function total() + { + if ($this->simple) { + throw new \DomainException('not support total'); + } + + return $this->total; + } + + public function listRows() + { + return $this->listRows; + } + + public function currentPage() + { + return $this->currentPage; + } + + public function lastPage() + { + if ($this->simple) { + throw new \DomainException('not support last'); + } + + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @access public + * @return boolean + */ + public function hasPages() + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @access public + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @access public + * @param string|null $fragment + * @return $this + */ + public function fragment($fragment) + { + $this->options['fragment'] = $fragment; + + return $this; + } + + /** + * 添加URL参数 + * + * @access public + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (!is_array($key)) { + $queries = [$key => $value]; + } else { + $queries = $key; + } + + foreach ($queries as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @access public + * @return string + */ + protected function buildFragment() + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @access public + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @access public + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @access public + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @access public + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @access public + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @access public + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + try { + $total = $this->total(); + } catch (\DomainException $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + $collection = $this->getCollection(); + + $result = call_user_func_array([$collection, $name], $arguments); + + if ($result === $collection) { + return $this; + } + + return $result; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/Process.php b/Server/application/common/Server/thinkphp/library/think/Process.php new file mode 100644 index 00000000..3b574db4 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Process.php @@ -0,0 +1,1268 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\process\exception\Failed as ProcessFailedException; +use think\process\exception\Timeout as ProcessTimeoutException; +use think\process\pipes\Pipes; +use think\process\pipes\Unix as UnixPipes; +use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; + +class Process +{ + + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + + private $useFileHandles = false; + + /** @var Pipes */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * @var array + */ + public static $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * 构造方法 + * @access public + * @param string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 + * @throws \RuntimeException + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) + { + if (!function_exists('proc_open')) { + throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->input = $input; + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR; + $this->pty = false; + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); + $this->options = array_replace([ + 'suppress_errors' => true, + 'binary_pipes' => true, + ], $options); + } + + public function __destruct() + { + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * 运行指令 + * @access public + * @param callback|null $callback + * @return int + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * 运行指令 + * @access public + * @param callable|null $callback + * @return self + * @throws \RuntimeException + * @throws ProcessFailedException + */ + public function mustRun($callback = null) + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + if (0 !== $this->run($callback)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * 启动进程并写到 STDIN 输入后返回。 + * @access public + * @param callable|null $callback + * @throws \RuntimeException + * @throws \RuntimeException + * @throws \LogicException + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + if ($this->outputDisabled && null !== $callback) { + throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); + } + $commandline .= '"'; + + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new \RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * 重启进程 + * @access public + * @param callable|null $callback + * @return Process + * @throws \RuntimeException + * @throws \RuntimeException + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * 等待要终止的进程 + * @access public + * @param callable|null $callback + * @return int + */ + public function wait($callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DIRECTORY_SEPARATOR || !$running; + $this->readPipes(true, $close); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * 获取PID + * @access public + * @return int|null + * @throws \RuntimeException + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * 将一个 POSIX 信号发送到进程中 + * @access public + * @param int $signal + * @return Process + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * 禁用从底层过程获取输出和错误输出。 + * @access public + * @return Process + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new \LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启从底层过程获取输出和错误输出。 + * @access public + * @return Process + * @throws \RuntimeException + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * 输出是否禁用 + * @access public + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * 获取当前的输出管道 + * @access public + * @return string + * @throws \LogicException + * @api + */ + public function getOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + return $this->stdout; + } + + /** + * 以增量方式返回的输出结果。 + * @access public + * @return string + */ + public function getIncrementalOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空输出 + * @access public + * @return Process + */ + public function clearOutput() + { + $this->stdout = ''; + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * 返回当前的错误输出的过程 (STDERR)。 + * @access public + * @return string + */ + public function getErrorOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + return $this->stderr; + } + + /** + * 以增量方式返回 errorOutput + * @access public + * @return string + */ + public function getIncrementalErrorOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空 errorOutput + * @access public + * @return Process + */ + public function clearErrorOutput() + { + $this->stderr = ''; + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * 获取退出码 + * @access public + * @return null|int + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * 获取退出文本 + * @access public + * @return null|string + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * 检查是否成功 + * @access public + * @return bool + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * 是否未捕获的信号已被终止子进程 + * @access public + * @return bool + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * 返回导致子进程终止其执行的数。 + * @access public + * @return int + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * 检查子进程信号是否已停止 + * @access public + * @return bool + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * 返回导致子进程停止其执行的数。 + * @access public + * @return int + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * 检查是否正在运行 + * @access public + * @return bool + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * 检查是否已开始 + * @access public + * @return bool + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * 检查是否已终止 + * @access public + * @return bool + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * 获取当前的状态 + * @access public + * @return string + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * 终止进程 + * @access public + */ + public function stop() + { + if ($this->isRunning()) { + if ('\\' === DIRECTORY_SEPARATOR && !$this->isSigchildEnabled()) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode > 0) { + throw new \RuntimeException('Unable to kill the process'); + } + } else { + $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); + foreach ($pids as $pid) { + if (is_numeric($pid)) { + posix_kill($pid, 9); + } + } + } + } + + $this->updateStatus(false); + if ($this->processInformation['running']) { + $this->close(); + } + + return $this->exitcode; + } + + /** + * 添加一行输出 + * @access public + * @param string $line + */ + public function addOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stdout .= $line; + } + + /** + * 添加一行错误输出 + * @access public + * @param string $line + */ + public function addErrorOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stderr .= $line; + } + + /** + * 获取被执行的指令 + * @access public + * @return string + */ + public function getCommandLine() +{ + return $this->commandline; + } + + /** + * 设置指令 + * @access public + * @param string $commandline + * @return self + */ + public function setCommandLine($commandline) +{ + $this->commandline = $commandline; + + return $this; + } + + /** + * 获取超时时间 + * @access public + * @return float|null + */ + public function getTimeout() +{ + return $this->timeout; + } + + /** + * 获取idle超时时间 + * @access public + * @return float|null + */ + public function getIdleTimeout() +{ + return $this->idleTimeout; + } + + /** + * 设置超时时间 + * @access public + * @param int|float|null $timeout + * @return self + */ + public function setTimeout($timeout) +{ + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置idle超时时间 + * @access public + * @param int|float|null $timeout + * @return self + */ + public function setIdleTimeout($timeout) +{ + if (null !== $timeout && $this->outputDisabled) { + throw new \LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置TTY + * @access public + * @param bool $tty + * @return self + */ + public function setTty($tty) +{ + if ('\\' === DIRECTORY_SEPARATOR && $tty) { + throw new \RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { + throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * 检查是否是tty模式 + * @access public + * @return bool + */ + public function isTty() +{ + return $this->tty; + } + + /** + * 设置pty模式 + * @access public + * @param bool $bool + * @return self + */ + public function setPty($bool) +{ + $this->pty = (bool) $bool; + + return $this; + } + + /** + * 是否是pty模式 + * @access public + * @return bool + */ + public function isPty() +{ + return $this->pty; + } + + /** + * 获取工作目录 + * @access public + * @return string|null + */ + public function getWorkingDirectory() +{ + if (null === $this->cwd) { + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * 设置工作目录 + * @access public + * @param string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) +{ + $this->cwd = $cwd; + + return $this; + } + + /** + * 获取环境变量 + * @access public + * @return array + */ + public function getEnv() +{ + return $this->env; + } + + /** + * 设置环境变量 + * @access public + * @param array $env + * @return self + */ + public function setEnv(array $env) +{ + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = []; + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * 获取输入 + * @access public + * @return null|string + */ + public function getInput() +{ + return $this->input; + } + + /** + * 设置输入 + * @access public + * @param mixed $input + * @return self + */ + public function setInput($input) +{ + if ($this->isRunning()) { + throw new \LogicException('Input can not be set while the process is running.'); + } + + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 获取proc_open的选项 + * @access public + * @return array + */ + public function getOptions() +{ + return $this->options; + } + + /** + * 设置proc_open的选项 + * @access public + * @param array $options + * @return self + */ + public function setOptions(array $options) +{ + $this->options = $options; + + return $this; + } + + /** + * 是否兼容windows + * @access public + * @return bool + */ + public function getEnhanceWindowsCompatibility() +{ + return $this->enhanceWindowsCompatibility; + } + + /** + * 设置是否兼容windows + * @access public + * @param bool $enhance + * @return self + */ + public function setEnhanceWindowsCompatibility($enhance) +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 返回是否 sigchild 兼容模式激活 + * @access public + * @return bool + */ + public function getEnhanceSigchildCompatibility() +{ + return $this->enhanceSigchildCompatibility; + } + + /** + * 激活 sigchild 兼容性模式。 + * @access public + * @param bool $enhance + * @return self + */ + public function setEnhanceSigchildCompatibility($enhance) +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 是否超时 + */ + public function checkTimeout() +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE); + } + } + + /** + * 是否支持pty + * @access public + * @return bool + */ + public static function isPtySupported() +{ + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + return $result = false; + } + + $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); + if (is_resource($proc)) { + proc_close($proc); + + return $result = true; + } + + return $result = false; + } + + /** + * 创建所需的 proc_open 的描述符 + * @access private + * @return array + */ + private function getDescriptors() +{ + if ('\\' === DIRECTORY_SEPARATOR) { + $this->processPipes = WindowsPipes::create($this, $this->input); + } else { + $this->processPipes = UnixPipes::create($this, $this->input); + } + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + + $descriptors = array_merge($descriptors, [['pipe', 'w']]); + + $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * 建立 wait () 使用的回调。 + * @access protected + * @param callable|null $callback + * @return callable + */ + protected function buildCallback($callback) +{ + $out = self::OUT; + $callback = function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * 更新状态 + * @access protected + * @param bool $blocking + */ + protected function updateStatus($blocking) +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + + $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + if (!$this->processInformation['running']) { + $this->close(); + } + } + + /** + * 是否开启 '--enable-sigchild' + * @access protected + * @return bool + */ + protected function isSigchildEnabled() +{ + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * 验证是否超时 + * @access private + * @param int|float|null $timeout + * @return float|null + */ + private function validateTimeout($timeout) +{ + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * 读取pipes + * @access private + * @param bool $blocking + * @param bool $close + */ + private function readPipes($blocking, $close) +{ + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } + } + } + + /** + * 捕获退出码 + */ + private function captureExitCode() +{ + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + /** + * 关闭资源 + * @access private + * @return int 退出码 + */ + private function close() +{ + $this->processPipes->close(); + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } else { + $exitcode = -1; + } + + $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] + && 0 < $this->processInformation['termsig'] + ) { + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + return $this->exitcode; + } + + /** + * 重置数据 + */ + private function resetProcessData() +{ + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * 将一个 POSIX 信号发送到进程中。 + * @access private + * @param int $signal + * @param bool $throwException + * @return bool + */ + private function doSignal($signal, $throwException) +{ + if (!$this->isRunning()) { + if ($throwException) { + throw new \LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ($this->isSigchildEnabled()) { + if ($throwException) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + return false; + } + + if (true !== @proc_terminate($this->process, $signal)) { + if ($throwException) { + throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + + $this->latestSignal = $signal; + + return true; + } + + /** + * 确保进程已经开启 + * @access private + * @param string $functionName + */ + private function requireProcessIsStarted($functionName) +{ + if (!$this->isStarted()) { + throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * 确保进程已经终止 + * @access private + * @param string $functionName + */ + private function requireProcessIsTerminated($functionName) +{ + if (!$this->isTerminated()) { + throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Request.php b/Server/application/common/Server/thinkphp/library/think/Request.php new file mode 100644 index 00000000..6b6dd4b4 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Request.php @@ -0,0 +1,2267 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\facade\Cookie; +use think\facade\Session; + +class Request +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'HTTP_X_REAL_IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + ]; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 主机名(含端口) + * @var string + */ + protected $host; + + /** + * 域名(含协议及端口) + * @var string + */ + protected $domain; + + /** + * 子域名 + * @var string + */ + protected $subDomain; + + /** + * 泛域名 + * @var string + */ + protected $panDomain; + + /** + * 当前URL地址 + * @var string + */ + protected $url; + + /** + * 基础URL + * @var string + */ + protected $baseUrl; + + /** + * 当前执行的文件 + * @var string + */ + protected $baseFile; + + /** + * 访问的ROOT地址 + * @var string + */ + protected $root; + + /** + * pathinfo + * @var string + */ + protected $pathinfo; + + /** + * pathinfo(不含后缀) + * @var string + */ + protected $path; + + /** + * 当前路由信息 + * @var array + */ + protected $routeInfo = []; + + /** + * 当前调度信息 + * @var \think\route\Dispatch + */ + protected $dispatch; + + /** + * 当前模块名 + * @var string + */ + protected $module; + + /** + * 当前控制器名 + * @var string + */ + protected $controller; + + /** + * 当前操作名 + * @var string + */ + protected $action; + + /** + * 当前语言集 + * @var string + */ + protected $langset; + + /** + * 当前请求参数 + * @var array + */ + protected $param = []; + + /** + * 当前GET参数 + * @var array + */ + protected $get = []; + + /** + * 当前POST参数 + * @var array + */ + protected $post = []; + + /** + * 当前REQUEST参数 + * @var array + */ + protected $request = []; + + /** + * 当前ROUTE参数 + * @var array + */ + protected $route = []; + + /** + * 当前PUT参数 + * @var array + */ + protected $put; + + /** + * 当前SESSION参数 + * @var array + */ + protected $session = []; + + /** + * 当前FILE参数 + * @var array + */ + protected $file = []; + + /** + * 当前COOKIE参数 + * @var array + */ + protected $cookie = []; + + /** + * 当前SERVER参数 + * @var array + */ + protected $server = []; + + /** + * 当前ENV参数 + * @var array + */ + protected $env = []; + + /** + * 当前HEADER参数 + * @var array + */ + protected $header = []; + + /** + * 资源类型定义 + * @var array + */ + protected $mimeType = [ + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', + ]; + + /** + * 当前请求内容 + * @var string + */ + protected $content; + + /** + * 全局过滤规则 + * @var array + */ + protected $filter; + + /** + * 扩展方法 + * @var array + */ + protected $hook = []; + + /** + * php://input内容 + * @var string + */ + protected $input; + + /** + * 请求缓存 + * @var array + */ + protected $cache; + + /** + * 缓存是否检查 + * @var bool + */ + protected $isCheckCache; + + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 架构函数 + * @access public + * @param array $options 参数 + */ + public function __construct(array $options = []) + { + $this->init($options); + + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function init(array $options = []) + { + $this->config = array_merge($this->config, $options); + + if (is_null($this->filter) && !empty($this->config['default_filter'])) { + $this->filter = $this->config['default_filter']; + } + } + + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + return isset($this->config[$name]) ? $this->config[$name] : null; + } + + public static function __make(App $app, Config $config) + { + $request = new static($config->pull('app')); + + $request->server = $_SERVER; + $request->env = $app['env']->get(); + + return $request; + } + + public function __call($method, $args) + { + if (array_key_exists($method, $this->hook)) { + array_unshift($args, $this); + return call_user_func_array($this->hook[$method], $args); + } + + throw new Exception('method not exists:' . static::class . '->' . $method); + } + + /** + * Hook 方法注入 + * @access public + * @param string|array $method 方法名 + * @param mixed $callback callable + * @return void + */ + public function hook($method, $callback = null) + { + if (is_array($method)) { + $this->hook = array_merge($this->hook, $method); + } else { + $this->hook[$method] = $callback; + } + } + + /** + * 创建一个URL请求 + * @access public + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content + * @return \think\Request + */ + public function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + { + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $info = parse_url($uri); + + if (isset($info['host'])) { + $server['SERVER_NAME'] = $info['host']; + $server['HTTP_HOST'] = $info['host']; + } + + if (isset($info['scheme'])) { + if ('https' === $info['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($info['port'])) { + $server['SERVER_PORT'] = $info['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; + } + + if (isset($info['user'])) { + $server['PHP_AUTH_USER'] = $info['user']; + } + + if (isset($info['pass'])) { + $server['PHP_AUTH_PW'] = $info['pass']; + } + + if (!isset($info['path'])) { + $info['path'] = '/'; + } + + $options = []; + $queryString = ''; + + $options[strtolower($method)] = $params; + + if (isset($info['query'])) { + parse_str(html_entity_decode($info['query']), $query); + if (!empty($params)) { + $params = array_replace($query, $params); + $queryString = http_build_query($params, '', '&'); + } else { + $params = $query; + $queryString = $info['query']; + } + } elseif (!empty($params)) { + $queryString = http_build_query($params, '', '&'); + } + + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + $options['cookie'] = $cookie; + $options['param'] = $params; + $options['file'] = $files; + $options['server'] = $server; + $options['url'] = $server['REQUEST_URI']; + $options['baseUrl'] = $info['path']; + $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); + $options['method'] = $server['REQUEST_METHOD']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; + $options['content'] = $content; + + $request = new static(); + foreach ($options as $name => $item) { + if (property_exists($request, $name)) { + $request->$name = $item; + } + } + + return $request; + } + + /** + * 获取当前包含协议、端口的域名 + * @access public + * @param bool $port 是否需要去除端口号 + * @return string + */ + public function domain($port = false) + { + return $this->scheme() . '://' . $this->host($port); + } + + /** + * 获取当前根域名 + * @access public + * @return string + */ + public function rootDomain() + { + $root = $this->config['url_domain_root']; + + if (!$root) { + $item = explode('.', $this->host(true)); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + + return $root; + } + + /** + * 获取当前子域名 + * @access public + * @return string + */ + public function subDomain() + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->config['url_domain_root']; + + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($this->host(true), $rootDomain, true), '.')); + } else { + $domain = explode('.', $this->host(true), -2); + } + + $this->subDomain = implode('.', $domain); + } + + return $this->subDomain; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setPanDomain($domain) + { + $this->panDomain = $domain; + return $this; + } + + /** + * 获取当前泛域名的值 + * @access public + * @return string + */ + public function panDomain() + { + return $this->panDomain; + } + + /** + * 设置当前完整URL 包括QUERY_STRING + * @access public + * @param string $url URL + * @return $this + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * 获取当前完整URL 包括QUERY_STRING + * @access public + * @param bool $complete 是否包含域名 + * @return string + */ + public function url($complete = false) + { + if (!$this->url) { + if ($this->isCli()) { + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $this->url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $this->url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $this->url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); + } else { + $this->url = ''; + } + } + + return $complete ? $this->domain() . $this->url : $this->url; + } + + /** + * 设置当前完整URL 不包括QUERY_STRING + * @access public + * @param string $url URL + * @return $this + */ + public function setBaseUrl($url) + { + $this->baseUrl = $url; + return $this; + } + + /** + * 获取当前URL 不含QUERY_STRING + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function baseUrl($domain = false) + { + if (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + + return $domain ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 设置或获取当前执行的文件 SCRIPT_NAME + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function baseFile($domain = false) + { + if (!$this->baseFile) { + $url = ''; + if (!$this->isCli()) { + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); + } + } + $this->baseFile = $url; + } + + return $domain ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置URL访问根地址 + * @access public + * @param string $url URL地址 + * @return string|$this + */ + public function setRoot($url = null) + { + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function root($domain = false) + { + if (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + + return $domain ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取URL访问根目录 + * @access public + * @return string + */ + public function rootUrl() + { + $base = $this->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base; + + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + + return $root; + } + + public function setPathinfo($pathinfo) + { + $this->pathinfo = $pathinfo; + return $this; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo() + { + if (is_null($this->pathinfo)) { + if (isset($_GET[$this->config['var_pathinfo']])) { + // 判断URL里面是否有兼容模式参数 + $pathinfo = $_GET[$this->config['var_pathinfo']]; + unset($_GET[$this->config['var_pathinfo']]); + unset($this->get[$this->config['var_pathinfo']]); + } elseif ($this->isCli()) { + // CLI模式下 index.php module/controller/action/params/... + $pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif ('cli-server' == PHP_SAPI) { + $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); + } elseif ($this->server('PATH_INFO')) { + $pathinfo = $this->server('PATH_INFO'); + } + + // 分析PATHINFO信息 + if (!isset($pathinfo)) { + foreach ($this->config['pathinfo_fetch'] as $type) { + if ($this->server($type)) { + $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ? + substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type); + break; + } + } + } + + if (!empty($pathinfo)) { + unset($this->get[$pathinfo], $this->request[$pathinfo]); + } + + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); + } + + return $this->pathinfo; + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path() + { + if (is_null($this->path)) { + $suffix = $this->config['url_html_suffix']; + $pathinfo = $this->pathinfo(); + + if (false === $suffix) { + // 禁止伪静态访问 + $this->path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); + } + } + + return $this->path; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext() + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time($float = false) + { + return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME'); + } + + /** + * 当前请求的资源类型 + * @access public + * @return false|string + */ + public function type() + { + $accept = $this->server('HTTP_ACCEPT'); + + if (empty($accept)) { + return false; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + + return false; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = '') + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 当前的请求类型 + * @access public + * @param bool $origin 是否获取原始请求类型 + * @return string + */ + public function method($origin = false) + { + if ($origin) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($_POST[$this->config['var_method']])) { + $method = strtolower($_POST[$this->config['var_method']]); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) { + $this->method = strtoupper($method); + $this->{$method} = $_POST; + } else { + $this->method = 'POST'; + } + unset($_POST[$this->config['var_method']]); + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); + } else { + $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + } + } + + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet() + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost() + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut() + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete() + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead() + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch() + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions() + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli() + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi() + { + return strpos(PHP_SAPI, 'cgi') === 0; + } + + /** + * 获取当前请求的参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function param($name = '', $default = null, $filter = '') + { + if (!$this->mergeParam) { + $method = $this->method(true); + + // 自动获取请求变量 + switch ($method) { + case 'POST': + $vars = $this->post(false); + break; + case 'PUT': + case 'DELETE': + case 'PATCH': + $vars = $this->put(false); + break; + default: + $vars = []; + } + + // 当前请求参数和URL地址中的参数合并 + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + + $this->mergeParam = true; + } + + if (true === $name) { + // 获取包含文件上传信息的数组 + $file = $this->file(); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + + return $this->input($data, '', $default, $filter); + } + + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置路由变量 + * @access public + * @param array $route 路由变量 + * @return $this + */ + public function setRouteVars(array $route) + { + $this->route = array_merge($this->route, $route); + return $this; + } + + /** + * 获取路由参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function route($name = '', $default = null, $filter = '') + { + return $this->input($this->route, $name, $default, $filter); + } + + /** + * 获取GET参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function get($name = '', $default = null, $filter = '') + { + if (empty($this->get)) { + $this->get = $_GET; + } + + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 获取POST参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (empty($this->post)) { + $this->post = !empty($_POST) ? $_POST : $this->getInputData($this->input); + } + + return $this->input($this->post, $name, $default, $filter); + } + + /** + * 获取PUT参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function put($name = '', $default = null, $filter = '') + { + if (is_null($this->put)) { + $this->put = $this->getInputData($this->input); + } + + return $this->input($this->put, $name, $default, $filter); + } + + protected function getInputData($content) + { + if (false !== strpos($this->contentType(), 'json')) { + return (array) json_decode($content, true); + } elseif (strpos($content, '=')) { + parse_str($content, $data); + return $data; + } + + return []; + } + + /** + * 获取DELETE参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function delete($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取PATCH参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (empty($this->request)) { + $this->request = $_REQUEST; + } + + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取session数据 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function session($name = '', $default = null) + { + if (empty($this->session)) { + $this->session = Session::get(); + } + + if ('' === $name) { + return $this->session; + } + + $data = $this->getData($this->session, $name); + + return is_null($data) ? $default : $data; + } + + /** + * 获取cookie参数 + * @access public + * @param string $name 变量名 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie($name = '', $default = null, $filter = '') + { + if (empty($this->cookie)) { + $this->cookie = Cookie::get(); + } + + if (!empty($name)) { + $data = Cookie::has($name) ? Cookie::get($name) : $default; + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function server($name = '', $default = null) + { + if (empty($name)) { + return $this->server; + } else { + $name = strtoupper($name); + } + + return isset($this->server[$name]) ? $this->server[$name] : $default; + } + + /** + * 获取上传的文件信息 + * @access public + * @param string $name 名称 + * @return null|array|\think\File + */ + public function file($name = '') + { + if (empty($this->file)) { + $this->file = isset($_FILES) ? $_FILES : []; + } + + $files = $this->file; + if (!empty($files)) { + if (strpos($name, '.')) { + list($name, $sub) = explode('.', $name); + } + + // 处理上传文件 + $array = $this->dealUploadFile($files, $name); + + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + + return; + } + + protected function dealUploadFile($files, $name) + { + $array = []; + foreach ($files as $key => $file) { + if ($file instanceof File) { + $array[$key] = $file; + } elseif (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + + for ($i = 0; $i < $count; $i++) { + if ($file['error'][$i] > 0) { + if ($name == $key) { + $this->throwUploadFileError($file['error'][$i]); + } else { + continue; + } + } + + $temp['key'] = $key; + + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + + $array[$key] = $item; + } else { + if ($file['error'] > 0) { + if ($key == $name) { + $this->throwUploadFileError($file['error']); + } else { + continue; + } + } + + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + + return $array; + } + + protected function throwUploadFileError($error) + { + static $fileUploadErrors = [ + 1 => 'upload File size exceeds the maximum value', + 2 => 'upload File size exceeds the maximum value', + 3 => 'only the portion of file is uploaded', + 4 => 'no file to uploaded', + 6 => 'upload temp dir not found', + 7 => 'file write error', + ]; + + $msg = $fileUploadErrors[$error]; + + throw new Exception($msg); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function env($name = '', $default = null) + { + if (empty($name)) { + return $this->env; + } else { + $name = strtoupper($name); + } + + return isset($this->env[$name]) ? $this->env[$name] : $default; + } + + /** + * 获取当前的Header + * @access public + * @param string $name header名称 + * @param string $default 默认值 + * @return string|array + */ + public function header($name = '', $default = null) + { + if (empty($this->header)) { + $header = []; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->server; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; + } + } + $this->header = array_change_key_case($header); + } + + if ('' === $name) { + return $this->header; + } + + $name = str_replace('_', '-', strtolower($name)); + + return isset($this->header[$name]) ? $this->header[$name] : $default; + } + + /** + * 递归重置数组指针 + * @access public + * @param array $data 数据源 + * @return void + */ + public function arrayReset(array &$data) + { + foreach ($data as &$value) { + if (is_array($value)) { + $this->arrayReset($value); + } + } + reset($data); + } + + /** + * 获取变量 支持过滤和默认值 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input($data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + list($name, $type) = explode('/', $name); + } + + $data = $this->getData($data, $name); + + if (is_null($data)) { + return $default; + } + + if (is_object($data)) { + return $data; + } + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + if (version_compare(PHP_VERSION, '7.1.0', '<')) { + // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针 + $this->arrayReset($data); + } + } else { + $this->filterValue($data, $name, $filter); + } + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + + return $data; + } + + /** + * 获取数据 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @return mixed + */ + protected function getData(array $data, $name) + { + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + return; + } + } + + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @access public + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } + + $this->filter = $filter; + } + + protected function getFilter($filter, $default) + { + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter) && false === strpos($filter, '/')) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + + return $filter; + } + + /** + * 递归过滤给定的值 + * @access public + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + private function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (false !== strpos($filter, '/')) { + // 正则过滤 + if (!preg_match($filter, $value)) { + // 匹配不成功返回默认值 + $value = $default; + break; + } + } elseif (!empty($filter)) { + // filter函数不存在时, 则使用filter_var进行过滤 + // filter为非整形值时, 调用filter_id取得过滤id + $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); + if (false === $value) { + $value = $default; + break; + } + } + } + } + + return $value; + } + + /** + * 强制类型转换 + * @access public + * @param string $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, $type) + { + switch (strtolower($type)) { + // 数组 + case 'a': + $data = (array) $data; + break; + // 数字 + case 'd': + $data = (int) $data; + break; + // 浮点 + case 'f': + $data = (float) $data; + break; + // 布尔 + case 'b': + $data = (boolean) $data; + break; + // 字符串 + case 's': + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + break; + } + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return mixed + */ + public function has($name, $type = 'param', $checkEmpty = false) + { + if (!in_array($type, ['param', 'get', 'post', 'request', 'put', 'patch', 'file', 'session', 'cookie', 'env', 'header', 'route'])) { + return false; + } + + if (empty($this->$type)) { + $param = $this->$type(); + } else { + $param = $this->$type; + } + + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function only($name, $type = 'param') + { + $param = $this->$type(); + + if (is_string($name)) { + $name = explode(',', $name); + } + + $item = []; + foreach ($name as $key => $val) { + + if (is_int($key)) { + $default = null; + $key = $val; + } else { + $default = $val; + } + + if (isset($param[$key])) { + $item[$key] = $param[$key]; + } elseif (isset($default)) { + $item[$key] = $default; + } + } + + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl() + { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { + return true; + } elseif ('https' == $this->server('REQUEST_SCHEME')) { + return true; + } elseif ('443' == $this->server('SERVER_PORT')) { + return true; + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { + return true; + } elseif ($this->config['https_agent_name'] && $this->server($this->config['https_agent_name'])) { + return true; + } + + return false; + } + + /** + * 当前是否JSON请求 + * @access public + * @return bool + */ + public function isJson() + { + return false !== strpos($this->type(), 'json'); + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax($ajax = false) + { + $value = $this->server('HTTP_X_REQUESTED_WITH'); + $result = 'xmlhttprequest' == strtolower($value) ? true : false; + + if (true === $ajax) { + return $result; + } + + $result = $this->param($this->config['var_ajax']) ? true : $result; + $this->mergeParam = false; + return $result; + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax($pjax = false) + { + $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + + if (true === $pjax) { + return $result; + } + + $result = $this->param($this->config['var_pjax']) ? true : $result; + $this->mergeParam = false; + return $result; + } + + /** + * 获取客户端IP地址 + * @access public + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ + public function ip($type = 0, $adv = true) + { + $type = $type ? 1 : 0; + static $ip = null; + + if (null !== $ip) { + return $ip[$type]; + } + + $httpAgentIp = $this->config['http_agent_ip']; + + if ($httpAgentIp && $this->server($httpAgentIp)) { + $ip = $this->server($httpAgentIp); + } elseif ($adv) { + if ($this->server('HTTP_X_FORWARDED_FOR')) { + $arr = explode(',', $this->server('HTTP_X_FORWARDED_FOR')); + $pos = array_search('unknown', $arr); + if (false !== $pos) { + unset($arr[$pos]); + } + $ip = trim(current($arr)); + } elseif ($this->server('HTTP_CLIENT_IP')) { + $ip = $this->server('HTTP_CLIENT_IP'); + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); + } + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); + } + + // IP地址类型 + $ip_mode = (strpos($ip, ':') === false) ? 'ipv4' : 'ipv6'; + + // IP地址合法验证 + if (filter_var($ip, FILTER_VALIDATE_IP) !== $ip) { + $ip = ('ipv4' === $ip_mode) ? '0.0.0.0' : '::'; + } + + // 如果是ipv4地址,则直接使用ip2long返回int类型ip;如果是ipv6地址,暂时不支持,直接返回0 + $long_ip = ('ipv4' === $ip_mode) ? sprintf("%u", ip2long($ip)) : 0; + + $ip = [$ip, $long_ip]; + + return $ip[$type]; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile() + { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) { + return true; + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) { + return true; + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { + return true; + } elseif ($this->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', $this->server('HTTP_USER_AGENT'))) { + return true; + } + + return false; + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme() + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query() + { + return $this->server('QUERY_STRING'); + } + + /** + * 设置当前请求的host(包含端口) + * @access public + * @param string $host 主机名(含端口) + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host($strict = false) + { + if (!$this->host) { + $this->host = $this->server('HTTP_X_REAL_HOST') ?: $this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'); + } + + return true === $strict && strpos($this->host, ':') ? strstr($this->host, ':', true) : $this->host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return integer + */ + public function port() + { + return $this->server('SERVER_PORT'); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return string + */ + public function protocol() + { + return $this->server('SERVER_PROTOCOL'); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return integer + */ + public function remotePort() + { + return $this->server('REMOTE_PORT'); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + + if ($contentType) { + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + + return ''; + } + + /** + * 获取当前请求的路由信息 + * @access public + * @param array $route 路由名称 + * @return array + */ + public function routeInfo(array $route = []) + { + if (!empty($route)) { + $this->routeInfo = $route; + } + + return $this->routeInfo; + } + + /** + * 设置或者获取当前请求的调度信息 + * @access public + * @param \think\route\Dispatch $dispatch 调度信息 + * @return \think\route\Dispatch + */ + public function dispatch($dispatch = null) + { + if (!is_null($dispatch)) { + $this->dispatch = $dispatch; + } + + return $this->dispatch; + } + + /** + * 获取当前请求的安全Key + * @access public + * @return string + */ + public function secureKey() + { + if (is_null($this->secureKey)) { + $this->secureKey = uniqid('', true); + } + + return $this->secureKey; + } + + /** + * 设置当前的模块名 + * @access public + * @param string $module 模块名 + * @return $this + */ + public function setModule($module) + { + $this->module = $module; + return $this; + } + + /** + * 设置当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return $this + */ + public function setController($controller) + { + $this->controller = $controller; + return $this; + } + + /** + * 设置当前的操作名 + * @access public + * @param string $action 操作名 + * @return $this + */ + public function setAction($action) + { + $this->action = $action; + return $this; + } + + /** + * 获取当前的模块名 + * @access public + * @return string + */ + public function module() + { + return $this->module ?: ''; + } + + /** + * 获取当前的控制器名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function controller($convert = false) + { + $name = $this->controller ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 获取当前的操作名 + * @access public + * @param bool $convert 转换为驼峰 + * @return string + */ + public function action($convert = false) + { + $name = $this->action ?: ''; + return $convert ? $name : strtolower($name); + } + + /** + * 设置当前的语言 + * @access public + * @param string $lang 语言名 + * @return $this + */ + public function setLangset($lang) + { + $this->langset = $lang; + return $this; + } + + /** + * 获取当前的语言 + * @access public + * @return string + */ + public function langset() + { + return $this->langset ?: ''; + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent() + { + if (is_null($this->content)) { + $this->content = $this->input; + } + + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function token($name = '__token__', $type = null) + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); + + if ($this->isAjax()) { + header($name . ': ' . $token); + } + + facade\Session::set($name, $token); + + return $token; + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return mixed + */ + public function cache($key, $expire = null, $except = [], $tag = null) + { + if (!is_array($except)) { + $tag = $except; + $except = []; + } + + if (false === $key || !$this->isGet() || $this->isCheckCache || false === $expire) { + // 关闭当前缓存 + return; + } + + // 标记请求缓存检查 + $this->isCheckCache = true; + + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); + } else { + return; + } + } + + if (isset($fun)) { + $key = $fun($key); + } + + $this->cache = [$key, $expire, $tag]; + return $this->cache; + } + + /** + * 读取请求缓存设置 + * @access public + * @return array + */ + public function getCache() + { + return $this->cache; + } + + /** + * 设置GET数据 + * @access public + * @param array $get 数据 + * @return $this + */ + public function withGet(array $get) + { + $this->get = $get; + return $this; + } + + /** + * 设置POST数据 + * @access public + * @param array $post 数据 + * @return $this + */ + public function withPost(array $post) + { + $this->post = $post; + return $this; + } + + /** + * 设置php://input数据 + * @access public + * @param string $input RAW数据 + * @return $this + */ + public function withInput($input) + { + $this->input = $input; + return $this; + } + + /** + * 设置文件上传数据 + * @access public + * @param array $files 上传信息 + * @return $this + */ + public function withFiles(array $files) + { + $this->file = $files; + return $this; + } + + /** + * 设置COOKIE数据 + * @access public + * @param array $cookie 数据 + * @return $this + */ + public function withCookie(array $cookie) + { + $this->cookie = $cookie; + return $this; + } + + /** + * 设置SERVER数据 + * @access public + * @param array $server 数据 + * @return $this + */ + public function withServer(array $server) + { + $this->server = array_change_key_case($server, CASE_UPPER); + return $this; + } + + /** + * 设置HEADER数据 + * @access public + * @param array $header 数据 + * @return $this + */ + public function withHeader(array $header) + { + $this->header = array_change_key_case($header); + return $this; + } + + /** + * 设置ENV数据 + * @access public + * @param array $env 数据 + * @return $this + */ + public function withEnv(array $env) + { + $this->env = $env; + return $this; + } + + /** + * 设置ROUTE变量 + * @access public + * @param array $route 数据 + * @return $this + */ + public function withRoute(array $route) + { + $this->route = $route; + return $this; + } + + /** + * 设置请求数据 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set($name, $value) + { + return $this->param[$name] = $value; + } + + /** + * 获取请求数据的值 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get($name) + { + return $this->param($name); + } + + /** + * 检测请求数据的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + return isset($this->param[$name]); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['dispatch'], $data['config']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Response.php b/Server/application/common/Server/thinkphp/library/think/Response.php new file mode 100644 index 00000000..5fa5402a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Response.php @@ -0,0 +1,429 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\response\Redirect as RedirectResponse; + +class Response +{ + /** + * 原始数据 + * @var mixed + */ + protected $data; + + /** + * 应用对象实例 + * @var App + */ + protected $app; + + /** + * 当前contentType + * @var string + */ + protected $contentType = 'text/html'; + + /** + * 字符集 + * @var string + */ + protected $charset = 'utf-8'; + + /** + * 状态码 + * @var integer + */ + protected $code = 200; + + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * header参数 + * @var array + */ + protected $header = []; + + /** + * 输出内容 + * @var string + */ + protected $content = null; + + /** + * 架构函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 + */ + public function __construct($data = '', $code = 200, array $header = [], $options = []) + { + $this->data($data); + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->contentType($this->contentType, $this->charset); + + $this->code = $code; + $this->app = Container::get('app'); + $this->header = array_merge($this->header, $header); + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response + */ + public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + + if (class_exists($class)) { + return new $class($data, $code, $header, $options); + } + + return new static($data, $code, $header, $options); + } + + /** + * 发送数据到客户端 + * @access public + * @return void + * @throws \InvalidArgumentException + */ + public function send() + { + // 监听response_send + $this->app['hook']->listen('response_send', $this); + + // 处理输出数据 + $data = $this->getContent(); + + // Trace调试注入 + if ('cli' != PHP_SAPI && $this->app['env']->get('app_trace', $this->app->config('app.app_trace'))) { + $this->app['debug']->inject($this, $data); + } + + if (200 == $this->code && $this->allowCache) { + $cache = $this->app['request']->getCache(); + if ($cache) { + $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; + $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; + + $this->app['cache']->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + } + } + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + header($name . (!is_null($val) ? ':' . $val : '')); + } + } + + $this->sendData($data); + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + + // 监听response_end + $this->app['hook']->listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + $this->app['session']->flush(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出数据 + * @access protected + * @param string $data 要处理的数据 + * @return void + */ + protected function sendData($data) + { + echo $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options($options = []) + { + $this->options = array_merge($this->options, $options); + + return $this; + } + + /** + * 输出数据设置 + * @access public + * @param mixed $data 输出数据 + * @return $this + */ + public function data($data) + { + $this->data = $data; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @param bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache($cache) + { + $this->allowCache = $cache; + + return $this; + } + + /** + * 设置响应头 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($name, $value = null) + { + if (is_array($name)) { + $this->header = array_merge($this->header, $name); + } else { + $this->header[$name] = $value; + } + + return $this; + } + + /** + * 设置页面输出内容 + * @access public + * @param mixed $content + * @return $this + */ + public function content($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * 发送HTTP状态 + * @access public + * @param integer $code 状态码 + * @return $this + */ + public function code($code) + { + $this->code = $code; + + return $this; + } + + /** + * LastModified + * @access public + * @param string $time + * @return $this + */ + public function lastModified($time) + { + $this->header['Last-Modified'] = $time; + + return $this; + } + + /** + * Expires + * @access public + * @param string $time + * @return $this + */ + public function expires($time) + { + $this->header['Expires'] = $time; + + return $this; + } + + /** + * ETag + * @access public + * @param string $eTag + * @return $this + */ + public function eTag($eTag) + { + $this->header['ETag'] = $eTag; + + return $this; + } + + /** + * 页面缓存控制 + * @access public + * @param string $cache 缓存设置 + * @return $this + */ + public function cacheControl($cache) + { + $this->header['Cache-control'] = $cache; + + return $this; + } + + /** + * 设置页面不做任何缓存 + * @access public + * @return $this + */ + public function noCache() + { + $this->header['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'; + $this->header['Pragma'] = 'no-cache'; + + return $this; + } + + /** + * 页面输出类型 + * @access public + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType($contentType, $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + + return $this; + } + + /** + * 获取头部信息 + * @access public + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader($name = '') + { + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } + + return $this->header; + } + + /** + * 获取原始数据 + * @access public + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @access public + * @return mixed + */ + public function getContent() + { + if (null == $this->content) { + $content = $this->output($this->data); + + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + } + + return $this->content; + } + + /** + * 获取状态码 + * @access public + * @return integer + */ + public function getCode() + { + return $this->code; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Route.php b/Server/application/common/Server/thinkphp/library/think/Route.php new file mode 100644 index 00000000..97f6dc7d --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Route.php @@ -0,0 +1,992 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\RouteNotFoundException; +use think\route\AliasRule; +use think\route\Dispatch; +use think\route\dispatch\Url as UrlDispatch; +use think\route\Domain; +use think\route\Resource; +use think\route\Rule; +use think\route\RuleGroup; +use think\route\RuleItem; + +class Route +{ + /** + * REST定义 + * @var array + */ + protected $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '//edit', 'edit'], + 'read' => ['get', '/', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/', 'update'], + 'delete' => ['delete', '/', 'delete'], + ]; + + /** + * 请求方法前缀定义 + * @var array + */ + protected $methodPrefix = [ + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', + ]; + + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 当前HOST + * @var string + */ + protected $host; + + /** + * 当前域名 + * @var string + */ + protected $domain; + + /** + * 当前分组对象 + * @var RuleGroup + */ + protected $group; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 路由绑定 + * @var array + */ + protected $bind = []; + + /** + * 域名对象 + * @var array + */ + protected $domains = []; + + /** + * 跨域路由规则 + * @var RuleGroup + */ + protected $cross; + + /** + * 路由别名 + * @var array + */ + protected $alias = []; + + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = true; + + /** + * 路由是否测试模式 + * @var bool + */ + protected $isTest; + + /** + * (分组)路由规则是否合并解析 + * @var bool + */ + protected $mergeRuleRegex = true; + + /** + * 路由解析自动搜索多级控制器 + * @var bool + */ + protected $autoSearchController = true; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->request = $app['request']; + $this->config = $config; + + $this->host = $this->request->host(true) ?: $config['app_host']; + + $this->setDefaultDomain(); + } + + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + + return isset($this->config[$name]) ? $this->config[$name] : null; + } + + /** + * 配置 + * @access public + * @param array $config + * @return void + */ + public function setConfig(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(App $app, Config $config) + { + $config = $config->pull('app'); + $route = new static($app, $config); + + $route->lazy($config['url_lazy_route']) + ->autoSearchController($config['controller_auto_search']) + ->mergeRuleRegex($config['route_rule_merge']); + + return $route; + } + + /** + * 设置路由的请求对象实例 + * @access public + * @param Request $request 请求对象实例 + * @return void + */ + public function setRequest($request) + { + $this->request = $request; + } + + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + $this->lazy = $lazy; + return $this; + } + + /** + * 设置路由为测试模式 + * @access public + * @param bool $test 路由是否测试模式 + * @return void + */ + public function setTestMode($test) + { + $this->isTest = $test; + } + + /** + * 检查路由是否为测试模式 + * @access public + * @return bool + */ + public function isTest() + { + return $this->isTest; + } + + /** + * 设置路由域名及分组(包括资源路由)是否合并解析 + * @access public + * @param bool $merge 路由是否合并解析 + * @return $this + */ + public function mergeRuleRegex($merge = true) + { + $this->mergeRuleRegex = $merge; + $this->group->mergeRuleRegex($merge); + + return $this; + } + + /** + * 设置路由自动解析是否搜索多级控制器 + * @access public + * @param bool $auto 是否自动搜索多级控制器 + * @return $this + */ + public function autoSearchController($auto = true) + { + $this->autoSearchController = $auto; + return $this; + } + + /** + * 初始化默认域名 + * @access protected + * @return void + */ + protected function setDefaultDomain() + { + // 默认域名 + $this->domain = $this->host; + + // 注册默认域名 + $domain = new Domain($this, $this->host); + + $this->domains[$this->host] = $domain; + + // 默认分组 + $this->group = $domain; + } + + /** + * 设置当前域名 + * @access public + * @param RuleGroup $group 域名 + * @return void + */ + public function setGroup(RuleGroup $group) + { + $this->group = $group; + } + + /** + * 获取当前分组 + * @access public + * @return RuleGroup + */ + public function getGroup() + { + return $this->group; + } + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this + */ + public function pattern($name, $rule = '') + { + $this->group->pattern($name, $rule); + + return $this; + } + + /** + * 注册路由参数 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value = '') + { + $this->group->option($name, $value); + + return $this; + } + + /** + * 注册域名路由 + * @access public + * @param string|array $name 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Domain + */ + public function domain($name, $rule = '', $option = [], $pattern = []) + { + // 支持多个域名使用相同路由规则 + $domainName = is_array($name) ? array_shift($name) : $name; + + if ('*' != $domainName && false === strpos($domainName, '.')) { + $domainName .= '.' . $this->request->rootDomain(); + } + + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); + + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); + } + + if (is_array($name) && !empty($name)) { + $root = $this->request->rootDomain(); + foreach ($name as $item) { + if (false === strpos($item, '.')) { + $item .= '.' . $root; + } + + $this->domains[$item] = $domainName; + } + } + + // 返回域名对象 + return $domain; + } + + /** + * 获取域名 + * @access public + * @return array + */ + public function getDomains() + { + return $this->domains; + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @param string $domain 域名 + * @return $this + */ + public function bind($bind, $domain = null) + { + $domain = is_null($domain) ? $this->domain : $domain; + + $this->bind[$domain] = $bind; + + return $this; + } + + /** + * 读取路由绑定 + * @access public + * @param string $domain 域名 + * @return string|null + */ + public function getBind($domain = null) + { + if (is_null($domain)) { + $domain = $this->domain; + } elseif (true === $domain) { + return $this->bind; + } elseif (false === strpos($domain, '.')) { + $domain .= '.' . $this->request->rootDomain(); + } + + $subDomain = $this->request->subDomain(); + + if (strpos($subDomain, '.')) { + $name = '*' . strstr($subDomain, '.'); + } + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($name) && isset($this->bind[$name])) { + $result = $this->bind[$name]; + } elseif (!empty($subDomain) && isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; + } + + /** + * 读取路由标识 + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @return mixed + */ + public function getName($name = null, $domain = null, $method = '*') + { + return $this->app['rule_name']->get($name, $domain, $method); + } + + /** + * 读取路由 + * @access public + * @param string $rule 路由规则 + * @param string $domain 域名 + * @return array + */ + public function getRule($rule, $domain = null) + { + if (is_null($domain)) { + $domain = $this->domain; + } + + return $this->app['rule_name']->getRule($rule, $domain); + } + + /** + * 读取路由 + * @access public + * @param string $domain 域名 + * @return array + */ + public function getRuleList($domain = null) + { + return $this->app['rule_name']->getRuleList($domain); + } + + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return $this + */ + public function setName($name) + { + $this->app['rule_name']->import($name); + return $this; + } + + /** + * 导入配置文件的路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $type 请求类型 + * @return void + */ + public function import(array $rules, $type = '*') + { + // 检查域名部署 + if (isset($rules['__domain__'])) { + foreach ($rules['__domain__'] as $key => $rule) { + $this->domain($key, $rule); + } + unset($rules['__domain__']); + } + + // 检查变量规则 + if (isset($rules['__pattern__'])) { + $this->pattern($rules['__pattern__']); + unset($rules['__pattern__']); + } + + // 检查路由别名 + if (isset($rules['__alias__'])) { + foreach ($rules['__alias__'] as $key => $val) { + $this->alias($key, $val); + } + unset($rules['__alias__']); + } + + // 检查资源路由 + if (isset($rules['__rest__'])) { + foreach ($rules['__rest__'] as $key => $rule) { + $this->resource($key, $rule); + } + unset($rules['__rest__']); + } + + // 检查路由规则(包含分组) + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + + if (empty($val)) { + continue; + } + + if (is_string($key) && 0 === strpos($key, '[')) { + $key = substr($key, 1, -1); + $this->group($key, $val); + } elseif (is_array($val)) { + $this->rule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + } else { + $this->rule($key, $val, $type); + } + } + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function rule($rule, $route, $method = '*', array $option = [], array $pattern = []) + { + return $this->group->addRule($rule, $route, $method, $option, $pattern); + } + + /** + * 设置跨域有效路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function setCrossDomainRule($rule, $method = '*') + { + if (!isset($this->cross)) { + $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex); + } + + $this->cross->addRuleItem($rule, $method); + + return $this; + } + + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function rules($rules, $method = '*', array $option = [], array $pattern = []) + { + $this->group->addRules($rules, $method, $option, $pattern); + } + + /** + * 注册路由分组 + * @access public + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $route 分组路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleGroup + */ + public function group($name, $route, array $option = [], array $pattern = []) + { + if (is_array($name)) { + $option = $name; + $name = isset($option['name']) ? $option['name'] : ''; + } + + return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); + } + + /** + * 注册路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function any($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern); + } + + /** + * 注册GET路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function get($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'GET', $option, $pattern); + } + + /** + * 注册POST路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function post($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'POST', $option, $pattern); + } + + /** + * 注册PUT路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function put($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'PUT', $option, $pattern); + } + + /** + * 注册DELETE路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function delete($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'DELETE', $option, $pattern); + } + + /** + * 注册PATCH路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function patch($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'PATCH', $option, $pattern); + } + + /** + * 注册资源路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Resource + */ + public function resource($rule, $route = '', array $option = [], array $pattern = []) + { + return (new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest)) + ->lazy($this->lazy); + } + + /** + * 注册控制器路由 操作方法对应不同的请求前缀 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleGroup + */ + public function controller($rule, $route = '', array $option = [], array $pattern = []) + { + $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern); + + foreach ($this->methodPrefix as $type => $val) { + $group->addRule('', $val . '', $type); + } + + return $group->prefix($route . '/'); + } + + /** + * 注册视图路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function view($rule, $template = '', array $vars = [], array $option = [], array $pattern = []) + { + return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars); + } + + /** + * 注册重定向路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $status 状态码 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function redirect($rule, $route = '', $status = 301, array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status); + } + + /** + * 注册别名路由 + * @access public + * @param string $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return AliasRule + */ + public function alias($rule, $route, array $option = []) + { + $aliasRule = new AliasRule($this, $this->group, $rule, $route, $option); + + $this->alias[$rule] = $aliasRule; + + return $aliasRule; + } + + /** + * 获取别名路由定义 + * @access public + * @param string $name 路由别名 + * @return string|array|null + */ + public function getAlias($name = null) + { + if (is_null($name)) { + return $this->alias; + } + + return isset($this->alias[$name]) ? $this->alias[$name] : null; + } + + /** + * 设置不同请求类型下面的方法前缀 + * @access public + * @param string|array $method 请求类型 + * @param string $prefix 类型前缀 + * @return $this + */ + public function setMethodPrefix($method, $prefix = '') + { + if (is_array($method)) { + $this->methodPrefix = array_merge($this->methodPrefix, array_change_key_case($method)); + } else { + $this->methodPrefix[strtolower($method)] = $prefix; + } + + return $this; + } + + /** + * 获取请求类型的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return string|null + */ + public function getMethodPrefix($method) + { + $method = strtolower($method); + + return isset($this->methodPrefix[$method]) ? $this->methodPrefix[$method] : null; + } + + /** + * rest方法定义和修改 + * @access public + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + + /** + * 获取rest方法定义的参数 + * @access public + * @param string $name 方法名称 + * @return array|null + */ + public function getRest($name = null) + { + if (is_null($name)) { + return $this->rest; + } + + return isset($this->rest[$name]) ? $this->rest[$name] : null; + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem + */ + public function miss($route, $method = '*', array $option = []) + { + return $this->group->addMissRule($route, $method, $option); + } + + /** + * 注册一个自动解析的URL路由 + * @access public + * @param string $route 路由地址 + * @return RuleItem + */ + public function auto($route) + { + return $this->group->addAutoRule($route); + } + + /** + * 检测URL路由 + * @access public + * @param string $url URL地址 + * @param bool $must 是否强制路由 + * @return Dispatch + * @throws RouteNotFoundException + */ + public function check($url, $must = false) + { + // 自动检测域名路由 + $domain = $this->checkDomain(); + $url = str_replace($this->config['pathinfo_depr'], '|', $url); + + $completeMatch = $this->config['route_complete_match']; + + $result = $domain->check($this->request, $url, $completeMatch); + + if (false === $result && !empty($this->cross)) { + // 检测跨域路由 + $result = $this->cross->check($this->request, $url, $completeMatch); + } + + if (false !== $result) { + // 路由匹配 + return $result; + } elseif ($must) { + // 强制路由不匹配则抛出异常 + throw new RouteNotFoundException(); + } + + // 默认路由解析 + return new UrlDispatch($this->request, $this->group, $url, [ + 'auto_search' => $this->autoSearchController, + ]); + } + + /** + * 检测域名的路由规则 + * @access protected + * @return Domain + */ + protected function checkDomain() + { + // 获取当前子域名 + $subDomain = $this->request->subDomain(); + + $item = false; + + if ($subDomain && count($this->domains) > 1) { + $domain = explode('.', $subDomain); + $domain2 = array_pop($domain); + + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + + if ($subDomain && isset($this->domains[$subDomain])) { + // 子域名配置 + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $this->domains['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($this->domains['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $this->domains['*']; + $panDomain = $domain2; + } + } + + if (isset($panDomain)) { + // 保存当前泛域名 + $this->request->setPanDomain($panDomain); + } + } + + if (false === $item) { + // 检测当前完整域名 + $item = $this->domains[$this->host]; + } + + if (is_string($item)) { + $item = $this->domains[$item]; + } + + return $item; + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear() + { + $this->app['rule_name']->clear(); + $this->group->clear(); + } + + /** + * 设置全局的路由分组参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup + */ + public function __call($method, $args) + { + return call_user_func_array([$this->group, $method], $args); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Session.php b/Server/application/common/Server/thinkphp/library/think/Session.php new file mode 100644 index 00000000..63ee7a03 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Session.php @@ -0,0 +1,579 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Session +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 是否初始化 + * @var bool + */ + protected $init = null; + + /** + * 锁驱动 + * @var object + */ + protected $lockDriver = null; + + /** + * 锁key + * @var string + */ + protected $sessKey = 'PHPSESSID'; + + /** + * 锁超时时间 + * @var integer + */ + protected $lockTimeout = 3; + + /** + * 是否启用锁机制 + * @var bool + */ + protected $lock = false; + + public function __construct(array $config = []) + { + $this->config = $config; + } + + /** + * 设置或者获取session作用域(前缀) + * @access public + * @param string $prefix + * @return string|void + */ + public function prefix($prefix = '') + { + empty($this->init) && $this->boot(); + + if (empty($prefix) && null !== $prefix) { + return $this->prefix; + } else { + $this->prefix = $prefix; + } + } + + public static function __make(Config $config) + { + return new static($config->pull('session')); + } + + /** + * 配置 + * @access public + * @param array $config + * @return void + */ + public function setConfig(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; + } + + if (isset($config['use_lock'])) { + $this->lock = $config['use_lock']; + } + } + + /** + * 设置已经初始化 + * @access public + * @return void + */ + public function inited() + { + $this->init = true; + } + + /** + * session初始化 + * @access public + * @param array $config + * @return void + * @throws \think\Exception + */ + public function init(array $config = []) + { + $config = $config ?: $this->config; + + $isDoStart = false; + if (isset($config['use_trans_sid'])) { + ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); + } + + // 启动session + if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.auto_start', 0); + $isDoStart = true; + } + + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; + } + + if (isset($config['use_lock'])) { + $this->lock = $config['use_lock']; + } + + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { + session_id($_REQUEST[$config['var_session_id']]); + } elseif (isset($config['id']) && !empty($config['id'])) { + session_id($config['id']); + } + + if (isset($config['name'])) { + session_name($config['name']); + } + + if (isset($config['path'])) { + session_save_path($config['path']); + } + + if (isset($config['domain'])) { + ini_set('session.cookie_domain', $config['domain']); + } + + if (isset($config['expire'])) { + ini_set('session.gc_maxlifetime', $config['expire']); + ini_set('session.cookie_lifetime', $config['expire']); + } + + if (isset($config['secure'])) { + ini_set('session.cookie_secure', $config['secure']); + } + + if (isset($config['httponly'])) { + ini_set('session.cookie_httponly', $config['httponly']); + } + + if (isset($config['use_cookies'])) { + ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); + } + + if (isset($config['cache_limiter'])) { + session_cache_limiter($config['cache_limiter']); + } + + if (isset($config['cache_expire'])) { + session_cache_expire($config['cache_expire']); + } + + if (!empty($config['type'])) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类 + if (!class_exists($class) || !session_set_save_handler(new $class($config))) { + throw new ClassNotFoundException('error session handler:' . $class, $class); + } + } + + if ($isDoStart) { + $this->start(); + } else { + $this->init = false; + } + + return $this; + } + + /** + * session自动启动或者初始化 + * @access public + * @return void + */ + public function boot() + { + if (is_null($this->init)) { + $this->init(); + } + + if (false === $this->init) { + if (PHP_SESSION_ACTIVE != session_status()) { + $this->start(); + } + $this->init = true; + } + } + + /** + * session设置 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function set($name, $value, $prefix = null) + { + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + if (strpos($name, '.')) { + // 二维数组赋值 + list($name1, $name2) = explode('.', $name); + if ($prefix) { + $_SESSION[$prefix][$name1][$name2] = $value; + } else { + $_SESSION[$name1][$name2] = $value; + } + } elseif ($prefix) { + $_SESSION[$prefix][$name] = $value; + } else { + $_SESSION[$name] = $value; + } + + $this->unlock(); + } + + /** + * session获取 + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public function get($name = '', $prefix = null) + { + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + + if ('' != $name) { + $name = explode('.', $name); + + foreach ($name as $val) { + if (isset($value[$val])) { + $value = $value[$val]; + } else { + $value = null; + break; + } + } + } + + $this->unlock(); + + return $value; + } + + /** + * session 读写锁驱动实例化 + */ + protected function initDriver() + { + $config = $this->config; + + if (!empty($config['type']) && isset($config['use_lock']) && $config['use_lock']) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类及类中是否存在 lock 和 unlock 函数 + if (class_exists($class) && method_exists($class, 'lock') && method_exists($class, 'unlock')) { + $this->lockDriver = new $class($config); + } + } + + // 通过cookie获得session_id + if (isset($config['name']) && $config['name']) { + $this->sessKey = $config['name']; + } + + if (isset($config['lock_timeout']) && $config['lock_timeout'] > 0) { + $this->lockTimeout = $config['lock_timeout']; + } + } + + /** + * session 读写加锁 + * @access protected + * @return void + */ + protected function lock() + { + if (empty($this->lock)) { + return; + } + + $this->initDriver(); + + if (null !== $this->lockDriver && method_exists($this->lockDriver, 'lock')) { + $t = time(); + // 使用 session_id 作为互斥条件,即只对同一 session_id 的会话互斥。第一次请求没有 session_id + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + + do { + if (time() - $t > $this->lockTimeout) { + $this->unlock(); + } + } while (!$this->lockDriver->lock($sessID, $this->lockTimeout)); + } + } + + /** + * session 读写解锁 + * @access protected + * @return void + */ + protected function unlock() + { + if (empty($this->lock)) { + return; + } + + $this->pause(); + + if ($this->lockDriver && method_exists($this->lockDriver, 'unlock')) { + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + $this->lockDriver->unlock($sessID); + } + } + + /** + * session获取并删除 + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public function pull($name, $prefix = null) + { + $result = $this->get($name, $prefix); + + if ($result) { + $this->delete($name, $prefix); + return $result; + } else { + return; + } + } + + /** + * session设置 下一次请求有效 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function flash($name, $value) + { + $this->set($name, $value); + + if (!$this->has('__flash__.__time__')) { + $this->set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + + $this->push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @access public + * @return void + */ + public function flush() + { + if (!$this->init) { + return; + } + + $item = $this->get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + $this->delete($item); + $this->set('__flash__', []); + } + } + } + + /** + * 删除session数据 + * @access public + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function delete($name, $prefix = null) + { + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + if (is_array($name)) { + foreach ($name as $key) { + $this->delete($key, $prefix); + } + } elseif (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + if ($prefix) { + unset($_SESSION[$prefix][$name1][$name2]); + } else { + unset($_SESSION[$name1][$name2]); + } + } else { + if ($prefix) { + unset($_SESSION[$prefix][$name]); + } else { + unset($_SESSION[$name]); + } + } + } + + /** + * 清空session数据 + * @access public + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function clear($prefix = null) + { + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + if ($prefix) { + unset($_SESSION[$prefix]); + } else { + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @access public + * @param string $name session名称 + * @param string|null $prefix + * @return bool + */ + public function has($name, $prefix = null) + { + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + + $name = explode('.', $name); + + foreach ($name as $val) { + if (!isset($value[$val])) { + return false; + } else { + $value = $value[$val]; + } + } + + return true; + } + + /** + * 添加数据到一个session数组 + * @access public + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key); + + if (is_null($array)) { + $array = []; + } + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * 启动session + * @access public + * @return void + */ + public function start() + { + session_start(); + + $this->init = true; + } + + /** + * 销毁session + * @access public + * @return void + */ + public function destroy() + { + if (!empty($_SESSION)) { + $_SESSION = []; + } + + session_unset(); + session_destroy(); + + $this->init = null; + $this->lockDriver = null; + } + + /** + * 重新生成session_id + * @access public + * @param bool $delete 是否删除关联会话文件 + * @return void + */ + public function regenerate($delete = false) + { + session_regenerate_id($delete); + } + + /** + * 暂停session + * @access public + * @return void + */ + public function pause() + { + // 暂停session + session_write_close(); + $this->init = false; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Template.php b/Server/application/common/Server/thinkphp/library/think/Template.php new file mode 100644 index 00000000..2855cbcb --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Template.php @@ -0,0 +1,1318 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\TemplateNotFoundException; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + protected $app; + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 模板配置参数 + * @var array + */ + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_base' => '', + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DIRECTORY_SEPARATOR, + 'cache_suffix' => 'php', // 默认模板缓存后缀 + 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 + 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 + 'tpl_begin' => '{', // 模板引擎普通标签开始标记 + 'tpl_end' => '}', // 模板引擎普通标签结束标记 + 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 + 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'compile_type' => 'file', // 模板编译类型 + 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 + 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'layout_on' => false, // 布局模板开关 + 'layout_name' => 'layout', // 布局模板入口文件 + 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'taglib_begin' => '{', // 标签库标签开始标记 + 'taglib_end' => '}', // 标签库标签结束标记 + 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + 'display_cache' => false, // 模板渲染缓存 + 'cache_id' => '', // 模板缓存ID + 'tpl_replace_string' => [], + 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 + ]; + + /** + * 保留内容信息 + * @var array + */ + private $literal = []; + + /** + * 模板包含信息 + * @var array + */ + private $includeFile = []; + + /** + * 模板存储对象 + * @var object + */ + protected $storage; + + /** + * 架构函数 + * @access public + * @param array $config + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config['cache_path'] = $app->getRuntimePath() . 'temp/'; + $this->config = array_merge($this->config, $config); + + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; + $this->config['taglib_end_origin'] = $this->config['taglib_end']; + + $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); + $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); + $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); + $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); + + // 初始化模板编译存储器 + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + + $this->storage = Loader::factory($type, '\\think\\template\\driver\\', null); + } + + public static function __make(Config $config) + { + return new static($config->pull('template')); + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name + * @param mixed $value + * @return void + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + } + + /** + * 模板引擎参数赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 模板引擎配置项 + * @access public + * @param array|string $config + * @return void|array + */ + public function config($config) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } elseif (isset($this->config[$config])) { + return $this->config[$config]; + } + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get($name = '') + { + if ('' == $name) { + return $this->data; + } + + $data = $this->data; + + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + + return $data; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + + if ($config) { + $this->config($config); + } + + $cache = $this->app['cache']; + + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 读取渲染缓存 + $cacheContent = $cache->get($this->config['cache_id']); + + if (false !== $cacheContent) { + echo $cacheContent; + return; + } + } + + $template = $this->parseTemplateFile($template); + + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 重新模板编译 + $content = file_get_contents($template); + $this->compiler($content, $cacheFile); + } + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + + // 获取并清空缓存 + $content = ob_get_clean(); + + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 缓存页面输出 + $cache->set($this->config['cache_id'], $content, $this->config['cache_time']); + } + + echo $content; + } + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($content, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + + if ($config) { + $this->config($config); + } + + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 模板编译 + $this->compiler($content, $cacheFile); + } + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + } + + /** + * 设置布局 + * @access public + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return object + */ + public function layout($name, $replace = '') + { + if (false === $name) { + // 关闭布局 + $this->config['layout_on'] = false; + } else { + // 开启布局 + $this->config['layout_on'] = true; + + // 名称必须为字符串 + if (is_string($name)) { + $this->config['layout_name'] = $name; + } + + if (!empty($replace)) { + $this->config['layout_item'] = $replace; + } + } + + return $this; + } + + /** + * 检查编译缓存是否有效 + * 如果无效则需要重新编译 + * @access private + * @param string $cacheFile 缓存文件名 + * @return boolean + */ + private function checkCache($cacheFile) + { + if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) { + return false; + } + + // 读取第一行 + preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + + if (!isset($matches[1])) { + return false; + } + + $includeFile = unserialize($matches[1]); + + if (!is_array($includeFile)) { + return false; + } + + // 检查模板文件是否有更新 + foreach ($includeFile as $path => $time) { + if (is_file($path) && filemtime($path) > $time) { + // 模板文件如果有更新则缓存需要更新 + return false; + } + } + + // 检查编译存储是否有效 + return $this->storage->check($cacheFile, $this->config['cache_time']); + } + + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache($cacheId) + { + if ($cacheId && $this->config['display_cache']) { + // 缓存页面输出 + return $this->app['cache']->has($cacheId); + } + + return false; + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(&$content, $cacheFile) + { + // 判断是否启用布局 + if ($this->config['layout_on']) { + if (false !== strpos($content, '{__NOLAYOUT__}')) { + // 可以单独定义不使用布局 + $content = str_replace('{__NOLAYOUT__}', '', $content); + } else { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + + if ($layoutFile) { + // 替换布局的主体内容 + $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + + // 模板解析 + $this->parse($content); + + if ($this->config['strip_space']) { + /* 去除html空格与换行 */ + $find = ['~>\s+<~', '~>(\s+\n|\r)~']; + $replace = ['><', '>']; + $content = preg_replace($find, $replace, $content); + } + + // 优化生成的php代码 + $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content); + + // 模板过滤输出 + $replace = $this->config['tpl_replace_string']; + $content = str_replace(array_keys($replace), array_values($replace), $content); + + // 添加安全代码及模板引用记录 + $content = 'includeFile) . '*/ ?>' . "\n" . $content; + // 编译存储 + $this->storage->write($cacheFile, $content); + + $this->includeFile = []; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(&$content) + { + // 内容为空不解析 + if (empty($content)) { + return; + } + + // 替换literal标签内容 + $this->parseLiteral($content); + + // 解析继承 + $this->parseExtend($content); + + // 解析布局 + $this->parseLayout($content); + + // 检查include语法 + $this->parseInclude($content); + + // 替换包含文件中literal标签内容 + $this->parseLiteral($content); + + // 检查PHP语法 + $this->parsePhp($content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if ($this->config['taglib_load']) { + $tagLibs = $this->getIncludeTagLib($content); + + if (!empty($tagLibs)) { + // 对导入的TagLib进行解析 + foreach ($tagLibs as $tagLibName) { + $this->parseTagLib($tagLibName, $content); + } + } + } + + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if ($this->config['taglib_pre_load']) { + $tagLibs = explode(',', $this->config['taglib_pre_load']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content); + } + } + + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',', $this->config['taglib_build_in']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content, true); + } + + // 解析普通模板标签 {$tagName} + $this->parseTag($content); + + // 还原被替换的Literal标签 + $this->parseLiteral($content, true); + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws \think\Exception + */ + private function parsePhp(&$content) + { + // 短标签的情况要将' . "\n", $content); + + // PHP语法检查 + if ($this->config['tpl_deny_php'] && false !== strpos($content, 'getRegex('layout'), $content, $matches)) { + // 替换Layout标签 + $content = str_replace($matches[0], '', $content); + // 解析Layout标签 + $array = $this->parseAttr($matches[0]); + + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($array['name']); + + if ($layoutFile) { + $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace, $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(&$content) + { + $regex = $this->getRegex('include'); + $func = function ($template) use (&$func, &$regex, &$content) { + if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array = $this->parseAttr($match[0]); + $file = $array['file']; + unset($array['file']); + + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($file); + + foreach ($array as $k => $v) { + // 以$开头字符串转换成模板变量 + if (0 === strpos($v, '$')) { + $v = $this->get(substr($v, 1)); + } + + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); + } + + $content = str_replace($match[0], $parseStr, $content); + // 再次对包含文件进行模板分析 + $func($parseStr); + } + unset($matches); + } + }; + + // 替换模板中的include标签 + $func($content); + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(&$content) + { + $regex = $this->getRegex('extend'); + $array = $blocks = $baseBlocks = []; + $extend = ''; + + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + if (preg_match($regex, $template, $matches)) { + if (!isset($array[$matches['name']])) { + $array[$matches['name']] = 1; + // 读取继承模板 + $extend = $this->parseTemplateName($matches['name']); + + // 递归检查继承 + $func($extend); + + // 取得block标签内容 + $blocks = array_merge($blocks, $this->parseBlock($template)); + + return; + } + } else { + // 取得顶层模板block标签内容 + $baseBlocks = $this->parseBlock($template, true); + + if (empty($extend)) { + // 无extend标签但有block标签的情况 + $extend = $template; + } + } + }; + + $func($content); + + if (!empty($extend)) { + if ($baseBlocks) { + $children = []; + foreach ($baseBlocks as $name => $val) { + $replace = $val['content']; + + if (!empty($children[$name])) { + // 如果包含有子block标签 + foreach ($children[$name] as $key) { + $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); + } + } + + if (isset($blocks[$name])) { + // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 + $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + + if (!empty($val['parent'])) { + // 如果不是最顶层的block标签 + $parent = $val['parent']; + + if (isset($blocks[$parent])) { + $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); + } + + $blocks[$name]['content'] = $replace; + $children[$parent][] = $name; + + continue; + } + } elseif (!empty($val['parent'])) { + // 如果子标签没有被继承则用原值 + $children[$val['parent']][] = $name; + $blocks[$name] = $val; + } + + if (!$val['parent']) { + // 替换模板中的顶级block标签 + $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); + } + } + } + + $content = $extend; + unset($blocks, $baseBlocks); + } + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(&$content, $restore = false) + { + $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + if (!$restore) { + $count = count($this->literal); + + // 替换literal标签 + foreach ($matches as $match) { + $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); + $content = str_replace($match[0], "", $content); + $count++; + } + } else { + // 还原literal标签 + foreach ($matches as $match) { + $content = str_replace($match[0], $this->literal[$match[1]], $content); + } + + // 清空literal记录 + $this->literal = []; + } + + unset($matches); + } + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(&$content, $sort = false) + { + $regex = $this->getRegex('block'); + $result = []; + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = $keys = []; + + foreach ($matches as $match) { + if (empty($match['name'][0])) { + if (count($right) > 0) { + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + + $result[$tag['name']] = [ + 'begin' => $tag['tag'], + 'content' => substr($content, $start, $length), + 'end' => $match[0][0], + 'parent' => count($right) ? end($right)['name'] : '', + ]; + + $keys[$tag['name']] = $match[0][1]; + } + } else { + // 标签头压入栈 + $right[] = [ + 'name' => $match[2][0], + 'offset' => $match[0][1], + 'tag' => $match[0][0], + ]; + } + } + + unset($right, $matches); + + if ($sort) { + // 按block标签结束符在模板中的位置排序 + array_multisort($keys, $result); + } + } + + return $result; + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access private + * @param string $content 模板内容 + * @return array|null + */ + private function getIncludeTagLib(&$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + + return explode(',', $matches['name']); + } + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib($tagLib, &$content, $hide = false) + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + + $tLib = new $className($this); + + $tLib->parseTag($content, $hide ? '' : $tagLib); + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr($str, $name = null) + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $array = []; + + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array[$match['name']] = $match['value']; + } + unset($matches); + } + + if (!empty($name) && isset($array[$name])) { + return $array[$name]; + } + + return $array; + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(&$content) + { + $regex = $this->getRegex('tag'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $str = stripslashes($match[1]); + $flag = substr($str, 0, 1); + + switch ($flag) { + case '$': + // 解析模板变量 格式 {$varName} + // 是否带有?号 + if (false !== $pos = strpos($str, '?')) { + $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); + $name = $array[0]; + + $this->parseVar($name); + //$this->parseVarFunction($name); + + $str = trim(substr($str, $pos + 1)); + $this->parseVar($str); + $first = substr($str, 0, 1); + + if (strpos($name, ')')) { + // $name为对象或是自动识别,或者含有函数 + if (isset($array[1])) { + $this->parseVar($array[2]); + $name .= $array[1] . $array[2]; + } + + switch ($first) { + case '?': + $this->parseVarFunction($name); + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $express = true; + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + default: + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + } + $str = ''; + } + } + } else { + $this->parseVar($str); + $this->parseVarFunction($str); + $str = ''; + } + break; + case ':': + // 输出某个函数的结果 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '~': + // 执行某个函数 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '-': + case '+': + // 输出计算 + $this->parseVar($str); + $str = ''; + break; + case '/': + // 注释标签 + $flag2 = substr($str, 1, 1); + if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) { + $str = ''; + } + break; + default: + // 未识别的标签直接返回 + $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; + break; + } + + $content = str_replace($match[0], $str, $content); + } + + unset($matches); + } + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(&$varStr) + { + $varStr = trim($varStr); + + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { + static $_varParseList = []; + + while ($matches[0]) { + $match = array_pop($matches[0]); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varParseList[$match[0]])) { + $parseStr = $_varParseList[$match[0]]; + } else { + if (strpos($match[0], '.')) { + $vars = explode('.', $match[0]); + $first = array_shift($vars); + + if ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } elseif ('$Request' == $first) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + + $parseStr = 'app(\'request\')->' . $method . '(' . $params . ')'; + } else { + switch ($this->config['tpl_var_identify']) { + case 'array': // 识别为数组 + $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']'; + break; + case 'obj': // 识别为对象 + $parseStr = $first . '->' . implode('->', $vars); + break; + default: // 自动判断数组或对象 + $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')'; + } + } + } else { + $parseStr = str_replace(':', '->', $match[0]); + } + + $_varParseList[$match[0]] = $parseStr; + } + + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); + } + unset($matches); + } + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 + * @return void + */ + public function parseVarFunction(&$varStr, $autoescape = true) + { + if (!$autoescape && false === strpos($varStr, '|')) { + return $varStr; + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { + $varStr .= '|' . $this->config['default_filter']; + } + + static $_varFunctionList = []; + + $_key = md5($varStr); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + + // 取得变量名称 + $name = trim(array_shift($varArray)); + + // 对变量使用函数 + $length = count($varArray); + + // 取得模板禁止使用函数列表 + $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + + for ($i = 0; $i < $length; $i++) { + $args = explode('=', $varArray[$i], 2); + + // 模板函数过滤 + $fun = trim($args[0]); + if (in_array($fun, $template_deny_funs)) { + continue; + } + + switch (strtolower($fun)) { + case 'raw': + break; + case 'date': + $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; + } else { + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; + } + } + } + } + + $_varFunctionList[$_key] = $name; + $varStr = $name; + } + return $varStr; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar($vars) + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + if ($vars) { + switch ($type) { + case 'SERVER': + $parseStr = 'app(\'request\')->server(\'' . $param . '\')'; + break; + case 'GET': + $parseStr = 'app(\'request\')->get(\'' . $param . '\')'; + break; + case 'POST': + $parseStr = 'app(\'request\')->post(\'' . $param . '\')'; + break; + case 'COOKIE': + $parseStr = 'app(\'cookie\')->get(\'' . $param . '\')'; + break; + case 'SESSION': + $parseStr = 'app(\'session\')->get(\'' . $param . '\')'; + break; + case 'ENV': + $parseStr = 'app(\'request\')->env(\'' . $param . '\')'; + break; + case 'REQUEST': + $parseStr = 'app(\'request\')->request(\'' . $param . '\')'; + break; + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'LANG': + $parseStr = 'app(\'lang\')->get(\'' . $param . '\')'; + break; + case 'CONFIG': + $parseStr = 'app(\'config\')->get(\'' . $param . '\')'; + break; + default: + $parseStr = '\'\''; + break; + } + } else { + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'app()->version()'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } + } + } + + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName) + { + $array = explode(',', $templateName); + $parseStr = ''; + + foreach ($array as $templateName) { + if (empty($templateName)) { + continue; + } + + if (0 === strpos($templateName, '$')) { + //支持加载变量文件名 + $templateName = $this->get(substr($templateName, 1)); + } + + $template = $this->parseTemplateFile($templateName); + + if ($template) { + // 获取模板文件内容 + $parseStr .= file_get_contents($template); + } + } + + return $parseStr; + } + + /** + * 解析模板文件名 + * @access private + * @param string $template 文件名 + * @return string|false + */ + private function parseTemplateFile($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + if (strpos($template, '@')) { + list($module, $template) = explode('@', $template); + } + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + + if ($this->config['view_base']) { + $module = isset($module) ? $module : $this->app['request']->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); + } else { + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; + } + + $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + + return $template; + } + + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex($tagName) + { + $regex = ''; + if ('tag' == $tagName) { + $begin = $this->config['tpl_begin']; + $end = $this->config['tpl_end']; + + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + } else { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + } + } else { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + + switch ($tagName) { + case 'block': + if ($single) { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; + } + break; + case 'literal': + if ($single) { + $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; + $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } else { + $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; + $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } + break; + case 'restoreliteral': + $regex = ''; + break; + case 'include': + $name = 'file'; + case 'taglib': + case 'layout': + case 'extend': + if (empty($name)) { + $name = 'name'; + } + if ($single) { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + + return '/' . $regex . '/is'; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['storage']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Url.php b/Server/application/common/Server/thinkphp/library/think/Url.php new file mode 100644 index 00000000..acd510aa --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Url.php @@ -0,0 +1,412 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Url +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * ROOT地址 + * @var string + */ + protected $root; + + /** + * 绑定检查 + * @var bool + */ + protected $bindCheck; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = $config; + + if (is_file($app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $app['route']->setName(include $app->getRuntimePath() . 'route.php'); + } + } + + /** + * 初始化 + * @access public + * @param array $config + * @return void + */ + public function init(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('app')); + } + + /** + * URL生成 支持路由反射 + * @access public + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @return string + */ + public function build($url = '', $vars = '', $suffix = true, $domain = false) + { + // 解析URL + if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { + // [name] 表示使用路由命名标识生成URL + $name = substr($url, 1, $pos - 1); + $url = 'name' . substr($url, $pos + 1); + } + + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + + if (false !== strpos($anchor, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + list($url, $domain) = explode('@', $url, 2); + } + } + + // 解析参数 + if (is_string($vars)) { + // aaa=1&bbb=2 转换成数组 + parse_str($vars, $vars); + } + + if ($url) { + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->app['route']->getName($checkName, $checkDomain); + + if (is_null($rule) && isset($info['query'])) { + $rule = $this->app['route']->getName($url); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { + // 匹配路由命名标识 + $url = $match[0]; + + if ($domain) { + $domain = $match[1]; + } + + if (!is_null($match[2])) { + $suffix = $match[2]; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检查别名路由 + $alias = $this->app['route']->getAlias(); + $matchAlias = false; + + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $item) { + $val = $item->getRoute(); + + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url); + } + + // 检测URL绑定 + if (!$this->bindCheck) { + $bind = $this->app['route']->getBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->app['route']->getBind(true); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + } + + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 还原URL分隔符 + $depr = $this->config['pathinfo_depr']; + $url = str_replace('/', $depr, $url); + + // URL后缀 + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if ($this->config['url_common_param']) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + $paramType = $this->config['url_param_type']; + + foreach ($vars as $var => $val) { + if ('' !== trim($val)) { + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } + } + } + + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + $url = $domain . rtrim($this->root ?: $this->app['request']->root(), '/') . '/' . ltrim($url, '/'); + + $this->bindCheck = false; + + return $url; + } + + // 直接解析URL地址 + protected function parseUrl($url) + { + $request = $this->app['request']; + + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } else { + // 解析到 模块/控制器/操作 + $module = $request->module(); + $module = $module ? $module . '/' : ''; + $controller = $request->controller(); + + if ('' == $url) { + $action = $request->action(); + } else { + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $module = empty($path) ? $module : array_pop($path) . '/'; + } + + if ($this->config['url_convert']) { + $action = strtolower($action); + $controller = Loader::parseName($controller); + } + + $url = $module . $controller . '/' . $action; + } + + return $url; + } + + // 检测域名 + protected function parseDomain(&$url, $domain) + { + if (!$domain) { + return ''; + } + + $rootDomain = $this->app['request']->rootDomain(); + if (true === $domain) { + // 自动判断域名 + $domain = $this->config['app_host'] ?: $this->app['request']->host(); + + $domains = $this->app['route']->getDomains(); + + if ($domains) { + $route_domain = array_keys($domains); + foreach ($route_domain as $domain_prefix) { + if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { + foreach ($domains as $key => $rule) { + $rule = is_array($rule) ? $rule[0] : $rule; + if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { + $url = ltrim($url, $rule); + $domain = $key; + + // 生成对应子域名 + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + + break; + } + } + } + } + } + } elseif (0 !== strpos($domain, $rootDomain) && false === strpos($domain, '.')) { + $domain .= '.' . $rootDomain; + } + + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $this->app['request']->isSsl() || $this->config['is_https'] ? 'https://' : 'http://'; + + } + + return $scheme . $domain; + } + + // 解析URL后缀 + protected function parseSuffix($suffix) + { + if ($suffix) { + $suffix = true === $suffix ? $this->config['url_html_suffix'] : $suffix; + + if ($pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; + } + + // 匹配路由地址 + public function getRuleUrl($rule, &$vars = [], $allowDomain = '') + { + $port = $this->app['request']->port(); + foreach ($rule as $item) { + list($url, $pattern, $domain, $suffix, $method) = $item; + + if (is_string($allowDomain) && $domain != $allowDomain) { + continue; + } + + if ($port && !in_array($port, [80, 443])) { + $domain .= ':' . $port; + } + + if (empty($pattern)) { + return [rtrim($url, '?/-'), $domain, $suffix]; + } + + $type = $this->config['url_common_param']; + $keys = []; + + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + $keys[] = $key; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } else { + $result = null; + $keys = []; + break; + } + } + + $vars = array_diff_key($vars, array_flip($keys)); + + if (isset($result)) { + return $result; + } + } + + return false; + } + + // 指定当前生成URL地址的root + public function root($root) + { + $this->root = $root; + $this->app['request']->setRoot($root); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/Validate.php b/Server/application/common/Server/thinkphp/library/think/Validate.php new file mode 100644 index 00000000..5fde7f31 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/Validate.php @@ -0,0 +1,1556 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\validate\ValidateRule; + +class Validate +{ + + /** + * 自定义验证类型 + * @var array + */ + protected static $type = []; + + /** + * 验证类型别名 + * @var array + */ + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + /** + * 当前验证规则 + * @var array + */ + protected $rule = []; + + /** + * 验证提示信息 + * @var array + */ + protected $message = []; + + /** + * 验证字段描述 + * @var array + */ + protected $field = []; + + /** + * 默认规则提示 + * @var array + */ + protected static $typeMsg = [ + 'require' => ':attribute require', + 'must' => ':attribute must', + 'number' => ':attribute must be numeric', + 'integer' => ':attribute must be integer', + 'float' => ':attribute must be float', + 'boolean' => ':attribute must be bool', + 'email' => ':attribute not a valid email address', + 'mobile' => ':attribute not a valid mobile', + 'array' => ':attribute must be a array', + 'accepted' => ':attribute must be yes,on or 1', + 'date' => ':attribute not a valid datetime', + 'file' => ':attribute not a valid file', + 'image' => ':attribute not a valid image', + 'alpha' => ':attribute must be alpha', + 'alphaNum' => ':attribute must be alpha-numeric', + 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', + 'activeUrl' => ':attribute not a valid domain or ip', + 'chs' => ':attribute must be chinese', + 'chsAlpha' => ':attribute must be chinese or alpha', + 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', + 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', + 'url' => ':attribute not a valid url', + 'ip' => ':attribute not a valid ip', + 'dateFormat' => ':attribute must be dateFormat of :rule', + 'in' => ':attribute must be in :rule', + 'notIn' => ':attribute be notin :rule', + 'between' => ':attribute must between :1 - :2', + 'notBetween' => ':attribute not between :1 - :2', + 'length' => 'size of :attribute must be :rule', + 'max' => 'max size of :attribute must be :rule', + 'min' => 'min size of :attribute must be :rule', + 'after' => ':attribute cannot be less than :rule', + 'before' => ':attribute cannot exceed :rule', + 'afterWith' => ':attribute cannot be less than :rule', + 'beforeWith' => ':attribute cannot exceed :rule', + 'expire' => ':attribute not within :rule', + 'allowIp' => 'access IP is not allowed', + 'denyIp' => 'access IP denied', + 'confirm' => ':attribute out of accord with :2', + 'different' => ':attribute cannot be same with :2', + 'egt' => ':attribute must greater than or equal :rule', + 'gt' => ':attribute must greater than :rule', + 'elt' => ':attribute must less than or equal :rule', + 'lt' => ':attribute must less than :rule', + 'eq' => ':attribute must equal :rule', + 'unique' => ':attribute has exists', + 'regex' => ':attribute not conform to the rules', + 'method' => 'invalid Request method', + 'token' => 'invalid token', + 'fileSize' => 'filesize not match', + 'fileExt' => 'extensions to upload is not allowed', + 'fileMime' => 'mimetype to upload is not allowed', + ]; + + /** + * 当前验证场景 + * @var array + */ + protected $currentScene = null; + + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; + + /** + * 内置正则验证规则 + * @var array + */ + protected $defaultRegex = [ + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3-9][0-9]\d{8}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/', + 'zip' => '/\d{6}/', + ]; + + /** + * 验证场景定义 + * @var array + */ + protected $scene = []; + + /** + * 验证失败错误信息 + * @var array + */ + protected $error = []; + + /** + * 是否批量验证 + * @var bool + */ + protected $batch = false; + + /** + * 场景需要验证的规则 + * @var array + */ + protected $only = []; + + /** + * 场景需要移除的验证规则 + * @var array + */ + protected $remove = []; + + /** + * 场景需要追加的验证规则 + * @var array + */ + protected $append = []; + + /** + * 验证正则定义 + * @var array + */ + protected $regex = []; + + /** + * 架构函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], array $message = [], array $field = []) + { + $this->rule = $rules + $this->rule; + $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); + } + + /** + * 创建一个验证器类 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + * @return Validate + */ + public static function make(array $rules = [], array $message = [], array $field = []) + { + return new self($rules, $message, $field); + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则或者字段描述信息 + * @return $this + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = $name + $this->rule; + if (is_array($rule)) { + $this->field = array_merge($this->field, $rule); + } + } else { + $this->rule[$name] = $rule; + } + + return $this; + } + + /** + * 注册扩展验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) + * @return void + */ + public static function extend($type, $callback = null) + { + if (is_array($type)) { + self::$type = array_merge(self::$type, $type); + } else { + self::$type[$type] = $callback; + } + } + + /** + * 设置验证规则的默认提示信息 + * @access public + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public static function setTypeMsg($type, $msg = null) + { + if (is_array($type)) { + self::$typeMsg = array_merge(self::$typeMsg, $type); + } else { + self::$typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param string|array $name 字段名称 + * @param string $message 提示信息 + * @return Validate + */ + public function message($name, $message = '') + { + if (is_array($name)) { + $this->message = array_merge($this->message, $name); + } else { + $this->message[$name] = $message; + } + + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string $name 场景名 + * @return $this + */ + public function scene($name) + { + // 设置当前场景 + $this->currentScene = $name; + + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return $this + */ + public function batch($batch = true) + { + $this->batch = $batch; + + return $this; + } + + /** + * 指定需要验证的字段列表 + * @access public + * @param array $fields 字段名 + * @return $this + */ + public function only($fields) + { + $this->only = $fields; + + return $this; + } + + /** + * 移除某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 null 移除所有规则 + * @return $this + */ + public function remove($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->remove[$field] = $rule; + } + + return $this; + } + + /** + * 追加某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 + * @return $this + */ + public function append($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + $this->append($key, $rule); + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->append[$field] = $rule; + } + + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 + * @return bool + */ + public function check($data, $rules = [], $scene = '') + { + $this->error = []; + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + // 获取场景定义 + $this->getScene($scene); + + foreach ($this->append as $key => $rule) { + if (!isset($rules[$key])) { + $rules[$key] = $rule; + unset($this->append[$key]); + } + } + + foreach ($rules as $key => $rule) { + // field => 'rule1|rule2...' field => ['rule1','rule2',...] + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + list($key, $title) = explode('|', $key); + } else { + $title = isset($this->field[$key]) ? $this->field[$key] : $key; + } + + // 场景检测 + if (!empty($this->only) && !in_array($key, $this->only)) { + continue; + } + + // 获取数据 支持多维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data, $title, $this]); + } elseif ($rule instanceof ValidateRule) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + if (is_array($result)) { + $this->error = array_merge($this->error, $result); + } else { + $this->error[$key] = $result; + } + } else { + $this->error = $result; + return false; + } + } + } + + return !empty($this->error) ? false : true; + } + + /** + * 根据验证规则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + public function checkRule($value, $rules) + { + if ($rules instanceof \Closure) { + return call_user_func_array($rules, [$value]); + } elseif ($rules instanceof ValidateRule) { + $rules = $rules->getRule(); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + list($type, $rule) = $this->getValidateType($key, $rule); + + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $result) { + return $result; + } + } + + return true; + } + + /** + * 验证单个字段规则 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 + * @return mixed + */ + protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) + { + if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { + // 字段已经移除 无需验证 + return true; + } + + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + + if (isset($this->append[$field])) { + // 追加额外的验证规则 + $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR); + unset($this->append[$field]); + } + + $i = 0; + $result = true; + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + list($type, $rule, $info) = $this->getValidateType($key, $rule); + + if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { + + } elseif (array_key_exists($field, $this->remove) && (null === $this->remove[$field] || in_array($info, $this->remove[$field]))) { + // 规则已经移除 + $i++; + continue; + } + + // 验证类型 + if (isset(self::$type[$type])) { + $result = call_user_func_array(self::$type[$type], [$value, $rule, $data, $field, $title]); + } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证数据 + $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (!empty($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = facade\Lang::get(substr($message, 2, -1)); + } + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace(':attribute', $title, $result); + + if (strpos($result, ':rule') && is_scalar($rule)) { + $result = str_replace(':rule', (string) $rule, $result); + } + } + + return $result; + } + $i++; + } + + return $result; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule) + { + // 判断验证类型 + if (!is_numeric($key)) { + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + list($type, $rule) = explode(':', $rule, 2); + if (isset($this->alias[$type])) { + // 判断别名 + $type = $this->alias[$type]; + } + $info = $type; + } elseif (method_exists($this, $rule)) { + $type = $rule; + $info = $rule; + $rule = ''; + } else { + $type = 'is'; + $info = $rule; + } + + return [$type, $rule, $info]; + } + + /** + * 验证是否和某个字段的值一致 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + public function confirm($value, $rule, $data = [], $field = '') + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function different($value, $rule, $data = []) + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function egt($value, $rule, $data = []) + { + return $value >= $this->getDataValue($data, $rule); + } + + /** + * 验证是否大于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function gt($value, $rule, $data) + { + return $value > $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function elt($value, $rule, $data = []) + { + return $value <= $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function lt($value, $rule, $data = []) + { + return $value < $this->getDataValue($data, $rule); + } + + /** + * 验证是否等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function eq($value, $rule) + { + return $value == $rule; + } + + /** + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function must($value, $rule = null) + { + return !empty($value) || '0' == $value; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + public function is($value, $rule, $data = []) + { + switch (Loader::parseName($rule, 1, false)) { + case 'require': + // 必须 + $result = !empty($value) || '0' == $value; + break; + case 'accepted': + // 接受 + $result = in_array($value, ['1', 'on', 'yes']); + break; + case 'date': + // 是否是一个有效日期 + $result = false !== strtotime($value); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'boolean': + case 'bool': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + break; + case 'number': + $result = ctype_digit((string) $value); + break; + case 'alphaNum': + $result = ctype_alnum($value); + break; + case 'array': + // 是否为数组 + $result = is_array($value); + break; + case 'file': + $result = $value instanceof File; + break; + case 'image': + $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); + break; + case 'token': + $result = $this->token($value, '__token__', $data); + break; + default: + if (isset(self::$type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array(self::$type[$rule], [$value]); + } elseif (function_exists('ctype_' . $rule)) { + // ctype验证规则 + $ctypeFun = 'ctype_' . $rule; + $result = $ctypeFun($value); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function activeUrl($value, $rule = 'MX') + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + public function ip($value, $rule = 'ipv4') + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 验证上传文件后缀 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileExt($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkExt($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkExt($rule); + } + + return false; + } + + /** + * 验证上传文件类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileMime($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkMime($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkMime($rule); + } + + return false; + } + + /** + * 验证上传文件大小 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileSize($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkSize($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkSize($rule); + } + + return false; + } + + /** + * 验证图片的宽高及类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function image($file, $rule) + { + if (!($file instanceof File)) { + return false; + } + + if ($rule) { + $rule = explode(',', $rule); + + list($width, $height, $type) = getimagesize($file->getRealPath()); + + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + + if ('jpg' == $imageType) { + $imageType = 'jpeg'; + } + + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + list($w, $h) = $rule; + + return $w == $width && $h == $height; + } + + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + + /** + * 验证请求类型 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function method($value, $rule) + { + $method = Container::get('request')->method(); + return strtoupper($rule) == $method; + } + + /** + * 验证时间和日期是否符合指定格式 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function dateFormat($value, $rule) + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + public function unique($value, $rule, $data, $field) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Container::get('app')->model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } + + $key = isset($rule[1]) ? $rule[1] : $field; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[] = [$key, '=', $data[$key]]; + } + } + } elseif (strpos($key, '=')) { + parse_str($key, $map); + } elseif (isset($data[$field])) { + $map[] = [$key, '=', $data[$field]]; + } else { + $map = []; + } + + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + + if (is_string($pk)) { + if (isset($rule[2])) { + $map[] = [$pk, '<>', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[] = [$pk, '<>', $data[$pk]]; + } + } + + if ($db->where($map)->field($pk)->find()) { + return false; + } + + return true; + } + + /** + * 使用行为类验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return mixed + */ + public function behavior($value, $rule, $data) + { + return Container::get('hook')->exec($rule, $data); + } + + /** + * 使用filter_var方式验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function filter($value, $rule) + { + if (is_string($rule) && strpos($rule, ',')) { + list($rule, $param) = explode(',', $rule); + } elseif (is_array($rule)) { + $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; + } else { + $param = null; + } + + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireIf($value, $rule, $data) + { + list($field, $val) = explode(',', $rule); + + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireCallback($value, $rule, $data) + { + $result = call_user_func_array([$this, $rule], [$value, $data]); + + if ($result) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWith($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + + if (!empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证是否在范围内 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function in($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notIn($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function between($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notBetween($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function length($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (strpos($rule, ',')) { + // 长度区间 + list($min, $max) = explode(',', $rule); + return $length >= $min && $length <= $max; + } + + // 指定长度 + return $length == $rule; + } + + /** + * 验证数据最大长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function max($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function min($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length >= $rule; + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function after($value, $rule, $data) + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function before($value, $rule, $data) + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function afterWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function beforeWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function expire($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + list($start, $end) = $rule; + + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; + } + + /** + * 验证IP许可 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + public function allowIp($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + public function denyIp($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return bool + */ + public function regex($value, $rule) + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } elseif (isset($this->defaultRegex[$rule])) { + $rule = $this->defaultRegex[$rule]; + } + + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 验证表单令牌 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function token($value, $rule, $data) + { + $rule = !empty($rule) ? $rule : '__token__'; + $session = Container::get('session'); + + if (!isset($data[$rule]) || !$session->has($rule)) { + // 令牌数据无效 + return false; + } + + // 令牌验证 + if (isset($data[$rule]) && $session->get($rule) === $data[$rule]) { + // 防止重复提交 + $session->delete($rule); // 验证完成销毁session + return true; + } + + // 开启TOKEN重置 + $session->delete($rule); + + return false; + } + + // 获取错误信息 + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持多维 + * @return mixed + */ + protected function getDataValue($data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (strpos($key, '.')) { + // 支持多维数组验证 + foreach (explode('.', $key) as $key) { + if (!isset($data[$key])) { + $value = null; + break; + } + $value = $data = $data[$key]; + } + } else { + $value = isset($data[$key]) ? $data[$key] : null; + } + + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string + */ + protected function getRuleMsg($attribute, $title, $type, $rule) + { + $lang = Container::get('lang'); + + if (isset($this->message[$attribute . '.' . $type])) { + $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; + } elseif (isset($this->message[$attribute])) { + $msg = $this->message[$attribute]; + } elseif (isset(self::$typeMsg[$type])) { + $msg = self::$typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = self::$typeMsg['require']; + } else { + $msg = $title . $lang->get('not conform to the rules'); + } + + if (!is_string($msg)) { + return $msg; + } + + if (0 === strpos($msg, '{%')) { + $msg = $lang->get(substr($msg, 2, -1)); + } elseif ($lang->has($msg)) { + $msg = $lang->get($msg); + } + + if (is_scalar($rule) && false !== strpos($msg, ':')) { + // 变量替换 + if (is_string($rule) && strpos($rule, ',')) { + $array = array_pad(explode(',', $rule), 3, ''); + } else { + $array = array_pad([], 3, ''); + } + $msg = str_replace( + [':attribute', ':1', ':2', ':3'], + [$title, $array[0], $array[1], $array[2]], + $msg); + if (strpos($msg, ':rule')) { + $msg = str_replace(':rule', (string) $rule, $msg); + } + } + + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return void + */ + protected function getScene($scene = '') + { + if (empty($scene)) { + // 读取指定场景 + $scene = $this->currentScene; + } + + $this->only = $this->append = $this->remove = []; + + if (empty($scene)) { + return; + } + + if (method_exists($this, 'scene' . $scene)) { + call_user_func([$this, 'scene' . $scene]); + } elseif (isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $scene = $this->scene[$scene]; + + if (is_string($scene)) { + $scene = explode(',', $scene); + } + + $this->only = $scene; + } + } + + /** + * 动态方法 直接调用is方法进行验证 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_push($args, lcfirst($method)); + + return call_user_func_array([$this, 'is'], $args); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/View.php b/Server/application/common/Server/thinkphp/library/think/View.php new file mode 100644 index 00000000..284dd41a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/View.php @@ -0,0 +1,253 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class View +{ + /** + * 模板引擎实例 + * @var object + */ + public $engine; + + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 内容过滤 + * @var mixed + */ + protected $filter; + + /** + * 全局模板变量 + * @var array + */ + protected static $var = []; + + /** + * 初始化 + * @access public + * @param mixed $engine 模板引擎参数 + * @return $this + */ + public function init($engine = []) + { + // 初始化模板引擎 + $this->engine($engine); + + return $this; + } + + public static function __make(Config $config) + { + return (new static())->init($config->pull('template')); + } + + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + + return $this; + } + + /** + * 清理模板变量 + * @access public + * @return void + */ + public function clear() + { + self::$var = []; + $this->data = []; + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + + return $this; + } + + /** + * 设置当前模板解析的引擎 + * @access public + * @param array|string $options 引擎参数 + * @return $this + */ + public function engine($options = []) + { + if (is_string($options)) { + $type = $options; + $options = []; + } else { + $type = !empty($options['type']) ? $options['type'] : 'Think'; + } + + if (isset($options['type'])) { + unset($options['type']); + } + + $this->engine = Loader::factory($type, '\\think\\view\\driver\\', $options); + + return $this; + } + + /** + * 配置模板引擎 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function config($name, $value = null) + { + $this->engine->config($name, $value); + + return $this; + } + + /** + * 检查模板是否存在 + * @access public + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->engine->exists($name); + } + + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter($filter) + { + if ($filter) { + $this->filter = $filter; + } + + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 + * @return string + * @throws \Exception + */ + public function fetch($template = '', $vars = [], $config = [], $renderContent = false) + { + // 模板变量 + $vars = array_merge(self::$var, $this->data, $vars); + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $method = $renderContent ? 'display' : 'fetch'; + $this->engine->$method($template, $vars, $config); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); + } + + return $content; + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @return mixed + */ + public function display($content, $vars = [], $config = []) + { + return $this->fetch($content, $vars, $config, true); + } + + /** + * 模板变量赋值 + * @access public + * @param string $name 变量名 + * @param mixed $value 变量值 + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * 取得模板显示变量的值 + * @access protected + * @param string $name 模板变量 + * @return mixed + */ + public function __get($name) + { + return $this->data[$name]; + } + + /** + * 检测模板变量是否设置 + * @access public + * @param string $name 模板变量名 + * @return bool + */ + public function __isset($name) + { + return isset($this->data[$name]); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/Driver.php b/Server/application/common/Server/thinkphp/library/think/cache/Driver.php new file mode 100644 index 00000000..64216810 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/Driver.php @@ -0,0 +1,366 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache; + +use think\Container; + +/** + * 缓存基础类 + */ +abstract class Driver +{ + /** + * 驱动句柄 + * @var object + */ + protected $handler = null; + + /** + * 缓存读取次数 + * @var integer + */ + protected $readTimes = 0; + + /** + * 缓存写入次数 + * @var integer + */ + protected $writeTimes = 0; + + /** + * 缓存参数 + * @var array + */ + protected $options = []; + + /** + * 缓存标签 + * @var string + */ + protected $tag; + + /** + * 序列化方法 + * @var array + */ + protected static $serialize = ['serialize', 'unserialize', 'think_serialize:', 16]; + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + abstract public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + abstract public function get($name, $default = false); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolean + */ + abstract public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function inc($name, $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function dec($name, $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + abstract public function rm($name); + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + abstract public function clear($tag = null); + + /** + * 获取有效期 + * @access protected + * @param integer|\DateTime $expire 有效期 + * @return integer + */ + protected function getExpireTime($expire) + { + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + + return $expire; + } + + /** + * 获取实际的缓存标识 + * @access protected + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull($name) + { + $result = $this->get($name, false); + + if ($result) { + $this->rm($name); + return $result; + } else { + return; + } + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + $time = time(); + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + + if ($value instanceof \Closure) { + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); + } + + // 缓存数据 + $this->set($name, $value, $expire); + + // 解锁 + $this->rm($name . '_lock'); + } catch (\Exception $e) { + $this->rm($name . '_lock'); + throw $e; + } catch (\throwable $e) { + $this->rm($name . '_lock'); + throw $e; + } + } else { + $value = $this->get($name); + } + + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($name)) { + + } elseif (is_null($keys)) { + $this->tag = $name; + } else { + $key = $this->getTagkey($name); + + if (is_string($keys)) { + $keys = explode(',', $keys); + } + + $keys = array_map([$this, 'getCacheKey'], $keys); + + if ($overlay) { + $value = $keys; + } else { + $value = array_unique(array_merge($this->getTagItem($name), $keys)); + } + + $this->set($key, implode(',', $value), 0); + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $key = $this->getTagkey($this->tag); + $this->tag = null; + + if ($this->has($key)) { + $value = explode(',', $this->get($key)); + $value[] = $name; + + if (count($value) > 1000) { + array_shift($value); + } + + $value = implode(',', array_unique($value)); + } else { + $value = $name; + } + + $this->set($key, $value, 0); + } + } + + /** + * 获取标签包含的缓存标识 + * @access protected + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $key = $this->getTagkey($tag); + $value = $this->get($key); + + if ($value) { + return array_filter(explode(',', $value)); + } else { + return []; + } + } + + protected function getTagKey($tag) + { + return 'tag_' . md5($tag); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data + * @return string + */ + protected function serialize($data) + { + if (is_scalar($data) || !$this->options['serialize']) { + return $data; + } + + $serialize = self::$serialize[0]; + + return self::$serialize[2] . $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data + * @return mixed + */ + protected function unserialize($data) + { + if ($this->options['serialize'] && 0 === strpos($data, self::$serialize[2])) { + $unserialize = self::$serialize[1]; + + return $unserialize(substr($data, self::$serialize[3])); + } else { + return $data; + } + } + + /** + * 注册序列化机制 + * @access public + * @param callable $serialize 序列化方法 + * @param callable $unserialize 反序列化方法 + * @param string $prefix 序列化前缀标识 + * @return $this + */ + public static function registerSerialize($serialize, $unserialize, $prefix = 'think_serialize:') + { + self::$serialize = [$serialize, $unserialize, $prefix, strlen($prefix)]; + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } + + public function getReadTimes() + { + return $this->readTimes; + } + + public function getWriteTimes() + { + return $this->writeTimes; + } + + public function __call($method, $args) + { + return call_user_func_array([$this->handler, $method], $args); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/File.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/File.php new file mode 100644 index 00000000..60be08db --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/File.php @@ -0,0 +1,307 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; +use think\Container; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class File extends Driver +{ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => '', + 'hash_type' => 'md5', + 'data_compress' => false, + 'serialize' => true, + ]; + + protected $expire; + + /** + * 架构函数 + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (empty($this->options['path'])) { + $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache' . DIRECTORY_SEPARATOR; + } elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; + } + + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolean + */ + private function init() + { + // 创建项目缓存目录 + try { + if (!is_dir($this->options['path']) && mkdir($this->options['path'], 0755, true)) { + return true; + } + } catch (\Exception $e) { + } + + return false; + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getCacheKey($name, $auto = false) + { + $name = hash($this->options['hash_type'], $name); + + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); + } + + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name; + } + + $filename = $this->options['path'] . $name . '.php'; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + } + } + + return $filename; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return false !== $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $filename = $this->getCacheKey($name); + + if (!is_file($filename)) { + return $default; + } + + $content = file_get_contents($filename); + $this->expire = null; + + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() > filemtime($filename) + $expire) { + //缓存过期删除缓存文件 + $this->unlink($filename); + return $default; + } + + $this->expire = $expire; + $content = substr($content, 32); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return $this->unserialize($content); + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $expire = $this->getExpireTime($expire); + $filename = $this->getCacheKey($name, true); + + if ($this->tag && !is_file($filename)) { + $first = true; + } + + $data = $this->serialize($value); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + + $data = "\n" . $data; + $result = file_put_contents($filename, $data); + + if ($result) { + isset($first) && $this->setTagItem($filename); + clearstatcache(); + return true; + } else { + return false; + } + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + $expire = $this->expire; + } else { + $value = $step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + $expire = $this->expire; + } else { + $value = -$step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + try { + return $this->unlink($this->getCacheKey($name)); + } catch (\Exception $e) { + } + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->unlink($key); + } + $this->rm($this->getTagKey($tag)); + return true; + } + + $this->writeTimes++; + + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*'); + + foreach ($files as $path) { + if (is_dir($path)) { + $matches = glob($path . DIRECTORY_SEPARATOR . '*.php'); + if (is_array($matches)) { + array_map(function ($v) { + $this->unlink($v); + }, $matches); + } + rmdir($path); + } else { + $this->unlink($path); + } + } + + return true; + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $path + * @return bool + * @author byron sampson + * @return boolean + */ + private function unlink($path) + { + return is_file($path) && unlink($path); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/Lite.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/Lite.php new file mode 100644 index 00000000..0cfe3907 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/Lite.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class Lite extends Driver +{ + protected $options = [ + 'prefix' => '', + 'path' => '', + 'expire' => 0, // 等于 10*365*24*3600(10年) + ]; + + /** + * 架构函数 + * @access public + * + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; + } + + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $filename = $this->getCacheKey($name); + + if (is_file($filename)) { + // 判断是否过期 + $mtime = filemtime($filename); + + if ($mtime < time()) { + // 清除已经过期的文件 + unlink($filename); + return $default; + } + + return include $filename; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 + * @return bool + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; + $expire = time() + $expire; + } + + $filename = $this->getCacheKey($name); + + if ($this->tag && !is_file($filename)) { + $first = true; + } + + $ret = file_put_contents($filename, ("setTagItem($filename); + touch($filename, $expire); + } + + return $ret; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + unlink($key); + } + + $this->rm($this->getTagKey($tag)); + return true; + } + + $this->writeTimes++; + + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*.php')); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/Memcache.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/Memcache.php new file mode 100644 index 00000000..1c535597 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/Memcache.php @@ -0,0 +1,206 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcache extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!extension_loaded('memcache')) { + throw new \BadFunctionCallException('not support: memcache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcache; + + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) : + $this->handler->addServer($host, $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + + return false !== $this->handler->get($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, 0, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + + foreach ($keys as $key) { + $this->handler->delete($key); + } + + $tagName = $this->getTagKey($tag); + $this->rm($tagName); + return true; + } + + $this->writeTimes++; + + return $this->handler->flush(); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/Memcached.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/Memcached.php new file mode 100644 index 00000000..4533e78a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/Memcached.php @@ -0,0 +1,279 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcached extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct($options = []) + { + if (!extension_loaded('memcached')) { + throw new \BadFunctionCallException('not support: memcached'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcached; + + if (!empty($this->options['option'])) { + $this->handler->setOptions($this->options['option']); + } + + // 设置连接超时时间(单位:毫秒) + if ($this->options['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); + } + + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + + $this->handler->addServers($servers); + $this->handler->setOption(\Memcached::OPT_COMPRESSION, false); + if ('' != $this->options['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + + return $this->handler->get($key) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + + $this->handler->deleteMulti($keys); + $this->rm($this->getTagKey($tag)); + + return true; + } + + $this->writeTimes++; + + return $this->handler->flush(); + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($keys)) { + $this->tag = $name; + } else { + $tagName = $this->getTagKey($name); + if ($overlay) { + $this->handler->delete($tagName); + } + + if (!$this->has($tagName)) { + $this->handler->set($tagName, ''); + } + + foreach ($keys as $key) { + $this->handler->append($tagName, ',' . $key); + } + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $tagName = $this->getTagKey($this->tag); + + if ($this->has($tagName)) { + $this->handler->append($tagName, ',' . $name); + } else { + $this->handler->set($tagName, $name); + } + + $this->tag = null; + } + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + public function getTagItem($tag) + { + $tagName = $this->getTagKey($tag); + return explode(',', trim($this->handler->get($tagName), ',')); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/Redis.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/Redis.php new file mode 100644 index 00000000..4eff2cf5 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/Redis.php @@ -0,0 +1,272 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 + * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 + * + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + * @author 尘缘 <130775@qq.com> + */ +class Redis extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->options as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) { + $params[$key] = $val; + unset($this->options[$key]); + } + } + + if ('' == $this->options['password']) { + unset($this->options['password']); + } + + $this->handler = new \Predis\Client($this->options, $params); + + $this->options['prefix'] = ''; + } else { + throw new \BadFunctionCallException('not support: redis'); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->handler->exists($this->getCacheKey($name)) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $value = $this->handler->get($this->getCacheKey($name)); + + if (is_null($value) || false === $value) { + return $default; + } + + return $this->unserialize($value); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + + $value = $this->serialize($value); + + if ($expire) { + $result = $this->handler->setex($key, $expire, $value); + } else { + $result = $this->handler->set($key, $value); + } + + isset($first) && $this->setTagItem($key); + + return $result; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return $this->handler->del($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + + $this->handler->del($keys); + + $tagName = $this->getTagKey($tag); + $this->handler->del($tagName); + return true; + } + + $this->writeTimes++; + + return $this->handler->flushDB(); + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($keys)) { + $this->tag = $name; + } else { + $tagName = $this->getTagKey($name); + if ($overlay) { + $this->handler->del($tagName); + } + + foreach ($keys as $key) { + $this->handler->sAdd($tagName, $key); + } + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $tagName = $this->getTagKey($this->tag); + $this->handler->sAdd($tagName, $name); + } + } + + /** + * 获取标签包含的缓存标识 + * @access protected + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $tagName = $this->getTagKey($tag); + return $this->handler->sMembers($tagName); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/Sqlite.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/Sqlite.php new file mode 100644 index 00000000..f57361e3 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/Sqlite.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Sqlite缓存驱动 + * @author liu21st + */ +class Sqlite extends Driver +{ + protected $options = [ + 'db' => ':memory:', + 'table' => 'sharedmemory', + 'prefix' => '', + 'expire' => 0, + 'persistent' => false, + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!extension_loaded('sqlite')) { + throw new \BadFunctionCallException('not support: sqlite'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + + $this->handler = $func($this->options['db']); + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . sqlite_escape_string($name); + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + + return sqlite_num_rows($result); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; + + $result = sqlite_query($this->handler, $sql); + + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if (function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + + return $this->unserialize($content); + } + + return $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + $name = $this->getCacheKey($name); + + $value = sqlite_escape_string($this->serialize($value)); + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 + } + + if (function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value, 3); + } + + if ($this->tag) { + $tag = $this->tag; + $this->tag = null; + } else { + $tag = ''; + } + + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + + if (sqlite_query($this->handler, $sql)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + $name = $this->getCacheKey($name); + + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + + return true; + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $name = sqlite_escape_string($this->getTagKey($tag)); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + + $this->writeTimes++; + + $sql = 'DELETE FROM ' . $this->options['table']; + + sqlite_query($this->handler, $sql); + + return true; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/Wincache.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/Wincache.php new file mode 100644 index 00000000..ef157841 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/Wincache.php @@ -0,0 +1,175 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + * @author liu21st + */ +class Wincache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('wincache_ucache_info')) { + throw new \BadFunctionCallException('not support: WinCache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + if (wincache_ucache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $keys = $this->getTagItem($tag); + + wincache_ucache_delete($keys); + + $tagName = $this->getTagkey($tag); + $this->rm($tagName); + return true; + } + + $this->writeTimes++; + return wincache_ucache_clear(); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/cache/driver/Xcache.php b/Server/application/common/Server/thinkphp/library/think/cache/driver/Xcache.php new file mode 100644 index 00000000..4e698597 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/cache/driver/Xcache.php @@ -0,0 +1,179 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Xcache缓存驱动 + * @author liu21st + */ +class Xcache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('xcache_info')) { + throw new \BadFunctionCallException('not support: Xcache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + + return xcache_isset($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return xcache_isset($key) ? $this->unserialize(xcache_get($key)) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if (xcache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return xcache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return xcache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return xcache_unset($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + + foreach ($keys as $key) { + xcache_unset($key); + } + + $this->rm($this->getTagKey($tag)); + return true; + } + + $this->writeTimes++; + + if (function_exists('xcache_unset_by_prefix')) { + return xcache_unset_by_prefix($this->options['prefix']); + } else { + return false; + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/config/driver/Ini.php b/Server/application/common/Server/thinkphp/library/think/config/driver/Ini.php new file mode 100644 index 00000000..b2a647d1 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/config/driver/Ini.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Ini +{ + protected $config; + + public function __construct($config) + { + $this->config = $config; + } + + public function parse() + { + if (is_file($this->config)) { + return parse_ini_file($this->config, true); + } else { + return parse_ini_string($this->config, true); + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/config/driver/Json.php b/Server/application/common/Server/thinkphp/library/think/config/driver/Json.php new file mode 100644 index 00000000..0d77c8ed --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/config/driver/Json.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Json +{ + protected $config; + + public function __construct($config) + { + if (is_file($config)) { + $config = file_get_contents($config); + } + + $this->config = $config; + } + + public function parse() + { + return json_decode($this->config, true); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/config/driver/Xml.php b/Server/application/common/Server/thinkphp/library/think/config/driver/Xml.php new file mode 100644 index 00000000..9d696338 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/config/driver/Xml.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Xml +{ + protected $config; + + public function __construct($config) + { + $this->config = $config; + } + + public function parse() + { + if (is_file($this->config)) { + $content = simplexml_load_file($this->config); + } else { + $content = simplexml_load_string($this->config); + } + + $result = (array) $content; + foreach ($result as $key => $val) { + if (is_object($val)) { + $result[$key] = (array) $val; + } + } + + return $result; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/Command.php b/Server/application/common/Server/thinkphp/library/think/console/Command.php new file mode 100644 index 00000000..a208e7b5 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/Command.php @@ -0,0 +1,482 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Command +{ + + /** @var Console */ + private $console; + private $name; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** + * 构造方法 + * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置 + * @throws \LogicException + * @api + */ + public function __construct($name = null) + { + $this->definition = new Definition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * 忽略验证错误 + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null) + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole() + { + return $this->console; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * 配置指令 + */ + protected function configure() + { + } + + /** + * 执行指令 + * @param Input $input + * @param Output $output + * @return null|int + * @throws \LogicException + * @see setCode() + */ + protected function execute(Input $input, Output $output) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * 用户验证 + * @param Input $input + * @param Output $output + */ + protected function interact(Input $input, Output $output) + { + } + + /** + * 初始化 + * @param Input $input An InputInterface instance + * @param Output $output An OutputInterface instance + */ + protected function initialize(Input $input, Output $output) + { + } + + /** + * 执行 + * @param Input $input + * @param Output $output + * @return int + * @throws \Exception + * @see setCode() + * @see execute() + */ + public function run(Input $input, Output $output) + { + $this->input = $input; + $this->output = $output; + + $this->getSynopsis(true); + $this->getSynopsis(false); + + $this->mergeConsoleDefinition(); + + try { + $input->bind($this->definition); + } catch (\Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 设置执行代码 + * @param callable $code callable(InputInterface $input, OutputInterface $output) + * @return Command + * @throws \InvalidArgumentException + * @see execute() + */ + public function setCode(callable $code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + + $this->code = $code; + + return $this; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition($mergeArgs = true) + { + if (null === $this->console + || (true === $this->consoleDefinitionMerged + && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs)) + ) { + return; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->console->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->console->getDefinition()->getOptions()); + + $this->consoleDefinitionMerged = true; + if ($mergeArgs) { + $this->consoleDefinitionMergedWithArgs = true; + } + } + + /** + * 设置参数定义 + * @param array|Definition $definition + * @return Command + * @api + */ + public function setDefinition($definition) + { + if ($definition instanceof Definition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->consoleDefinitionMerged = false; + + return $this; + } + + /** + * 获取参数定义 + * @return Definition + * @api + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new Argument($name, $mode, $description, $default)); + + return $this; + } + + /** + * 添加选项 + * @param string $name 选项名称 + * @param string $shortcut 别名 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * 设置指令名称 + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp() + { + return $this->help; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $_SERVER['PHP_SELF'] . ' ' . $name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + + /** + * 设置别名 + * @param string[] $aliases + * @return Command + * @throws \InvalidArgumentException + */ + public function setAliases($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * 添加用法介绍 + * @param string $usage + * @return $this + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws \InvalidArgumentException + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } + + /** + * 输出表格 + * @param Table $table + * @return string + */ + protected function table(Table $table) + { + $content = $table->render(); + $this->output->writeln($content); + return $content; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/Input.php b/Server/application/common/Server/thinkphp/library/think/console/Input.php new file mode 100644 index 00000000..2482dfdc --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/Input.php @@ -0,0 +1,464 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Input +{ + + /** + * @var Definition + */ + protected $definition; + + /** + * @var Option[] + */ + protected $options = []; + + /** + * @var Argument[] + */ + protected $arguments = []; + + protected $interactive = true; + + private $tokens; + private $parsed; + + public function __construct($argv = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + // 去除命令名 + array_shift($argv); + } + + $this->tokens = $argv; + + $this->definition = new Definition(); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * 绑定实例 + * @param Definition $definition A InputDefinition instance + */ + public function bind(Definition $definition) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * 解析短选项 + * @param string $token 当前的指令. + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) + && $this->definition->getOptionForShortcut($name[0])->acceptValue() + ) { + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * 解析短选项 + * @param string $name 当前指令 + * @throws \RuntimeException + */ + private function parseShortOptionSet($name) + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * 解析完整选项 + * @param string $token 当前指令 + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } else { + $this->addLongOption($name, null); + } + } + + /** + * 解析参数 + * @param string $token 当前指令 + * @throws \RuntimeException + */ + private function parseArgument($token) + { + $c = count($this->arguments); + + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + + $this->arguments[$arg->getName()][] = $token; + } else { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * 添加一个短选项的值 + * @param string $shortcut 短名称 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * 添加一个完整选项的值 + * @param string $name 选项名 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = ''; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * 获取第一个参数 + * @return string|null + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + return; + } + + /** + * 检查原始参数是否包含某个值 + * @param string|array $values 需要检查的值 + * @return bool + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + return true; + } + } + } + + return false; + } + + /** + * 获取原始选项的值 + * @param string|array $values 需要检查的值 + * @param mixed $default 默认值 + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * 验证输入 + * @throws \RuntimeException + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * 检查输入是否是交互的 + * @return bool + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * 检查是否存在某个参数 + * @param string|int $name 参数名或位置 + * @return bool + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * 是否有某个选项 + * @param string $name 选项名 + * @return bool + */ + public function hasOption($name) + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * 返回传递给命令的参数的字符串 + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . $this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/LICENSE b/Server/application/common/Server/thinkphp/library/think/console/LICENSE new file mode 100644 index 00000000..0abe056e --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Server/application/common/Server/thinkphp/library/think/console/Output.php b/Server/application/common/Server/thinkphp/library/think/console/Output.php new file mode 100644 index 00000000..65dc9fb8 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/Output.php @@ -0,0 +1,222 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use Exception; +use think\console\output\Ask; +use think\console\output\Descriptor; +use think\console\output\driver\Buffer; +use think\console\output\driver\Console; +use think\console\output\driver\Nothing; +use think\console\output\Question; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +/** + * Class Output + * @package think\console + * + * @see \think\console\output\driver\Console::setDecorated + * @method void setDecorated($decorated) + * + * @see \think\console\output\driver\Buffer::fetch + * @method string fetch() + * + * @method void info($message) + * @method void error($message) + * @method void comment($message) + * @method void warning($message) + * @method void highlight($message) + * @method void question($message) + */ +class Output +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + private $verbosity = self::VERBOSITY_NORMAL; + + /** @var Buffer|Console|Nothing */ + private $handle = null; + + protected $styles = [ + 'info', + 'error', + 'comment', + 'question', + 'highlight', + 'warning' + ]; + + public function __construct($driver = 'console') + { + $class = '\\think\\console\\output\\driver\\' . ucwords($driver); + + $this->handle = new $class($this); + } + + public function ask(Input $input, $question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function askHidden(Input $input, $question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function confirm(Input $input, $question, $default = true) + { + return $this->askQuestion($input, new Confirmation($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(Input $input, $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion($input, new Choice($question, $choices, $default)); + } + + protected function askQuestion(Input $input, Question $question) + { + $ask = new Ask($input, $this, $question); + $answer = $ask->run(); + + if ($input->isInteractive()) { + $this->newLine(); + } + + return $answer; + } + + protected function block($style, $message) + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine($count = 1) + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(\Exception $e) + { + $this->handle->renderException($e); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []) + { + $descriptor = new Descriptor(); + $options = array_merge([ + 'raw_text' => false, + ], $options); + + $descriptor->describe($this, $object, $options); + } + + public function __call($method, $args) + { + if (in_array($method, $this->styles)) { + array_unshift($args, $method); + return call_user_func_array([$this, 'block'], $args); + } + + if ($this->handle && method_exists($this->handle, $method)) { + return call_user_func_array([$this->handle, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/Table.php b/Server/application/common/Server/thinkphp/library/think/console/Table.php new file mode 100644 index 00000000..9e28e266 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/Table.php @@ -0,0 +1,281 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +class Table +{ + const ALIGN_LEFT = 1; + const ALIGN_RIGHT = 0; + const ALIGN_CENTER = 2; + + /** + * 头信息数据 + * @var array + */ + protected $header = []; + + /** + * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $headerAlign = 1; + + /** + * 表格数据(二维数组) + * @var array + */ + protected $rows = []; + + /** + * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $cellAlign = 1; + + /** + * 单元格宽度信息 + * @var array + */ + protected $colWidth = []; + + /** + * 表格输出样式 + * @var string + */ + protected $style = 'default'; + + /** + * 表格样式定义 + * @var array + */ + protected $format = [ + 'compact' => [], + 'default' => [ + 'top' => ['+', '-', '+', '+'], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['+', '-', '+', '+'], + 'bottom' => ['+', '-', '+', '+'], + 'cross-top' => ['+', '-', '-', '+'], + 'cross-bottom' => ['+', '-', '-', '+'], + ], + 'markdown' => [ + 'top' => [' ', ' ', ' ', ' '], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['|', '-', '|', '|'], + 'bottom' => [' ', ' ', ' ', ' '], + 'cross-top' => ['|', ' ', ' ', '|'], + 'cross-bottom' => ['|', ' ', ' ', '|'], + ], + 'borderless' => [ + 'top' => ['=', '=', ' ', '='], + 'cell' => [' ', ' ', ' ', ' '], + 'middle' => ['=', '=', ' ', '='], + 'bottom' => ['=', '=', ' ', '='], + 'cross-top' => ['=', '=', ' ', '='], + 'cross-bottom' => ['=', '=', ' ', '='], + ], + 'box' => [ + 'top' => ['┌', '─', '┬', '┐'], + 'cell' => ['│', ' ', '│', '│'], + 'middle' => ['├', '─', '┼', '┤'], + 'bottom' => ['└', '─', '┴', '┘'], + 'cross-top' => ['├', '─', '┴', '┤'], + 'cross-bottom' => ['├', '─', '┬', '┤'], + ], + 'box-double' => [ + 'top' => ['╔', '═', '╤', '╗'], + 'cell' => ['║', ' ', '│', '║'], + 'middle' => ['╠', '─', '╪', '╣'], + 'bottom' => ['╚', '═', '╧', '╝'], + 'cross-top' => ['╠', '═', '╧', '╣'], + 'cross-bottom' => ['╠', '═', '╤', '╣'], + ], + ]; + + /** + * 设置表格头信息 以及对齐方式 + * @access public + * @param array $header 要输出的Header信息 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setHeader(array $header, $align = self::ALIGN_LEFT) + { + $this->header = $header; + $this->headerAlign = $align; + + $this->checkColWidth($header); + } + + /** + * 设置输出表格数据 及对齐方式 + * @access public + * @param array $rows 要输出的表格数据(二维数组) + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setRows(array $rows, $align = self::ALIGN_LEFT) + { + $this->rows = $rows; + $this->cellAlign = $align; + + foreach ($rows as $row) { + $this->checkColWidth($row); + } + } + + /** + * 检查列数据的显示宽度 + * @access public + * @param mixed $row 行数据 + * @return void + */ + protected function checkColWidth($row) + { + if (is_array($row)) { + foreach ($row as $key => $cell) { + if (!isset($this->colWidth[$key]) || strlen($cell) > $this->colWidth[$key]) { + $this->colWidth[$key] = strlen($cell); + } + } + } + } + + /** + * 增加一行表格数据 + * @access public + * @param mixed $row 行数据 + * @param bool $first 是否在开头插入 + * @return void + */ + public function addRow($row, $first = false) + { + if ($first) { + array_unshift($this->rows, $row); + } else { + $this->rows[] = $row; + } + + $this->checkColWidth($row); + } + + /** + * 设置输出表格的样式 + * @access public + * @param string $style 样式名 + * @return void + */ + public function setStyle($style) + { + $this->style = isset($this->format[$style]) ? $style : 'default'; + } + + /** + * 输出分隔行 + * @access public + * @param string $pos 位置 + * @return string + */ + protected function renderSeparator($pos) + { + $style = $this->getStyle($pos); + $array = []; + + foreach ($this->colWidth as $width) { + $array[] = str_repeat($style[1], $width + 2); + } + + return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL; + } + + /** + * 输出表格头部 + * @access public + * @return string + */ + protected function renderHeader() + { + $style = $this->getStyle('cell'); + $content = $this->renderSeparator('top'); + + foreach ($this->header as $key => $header) { + $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign); + } + + if (!empty($array)) { + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + + if ($this->rows) { + $content .= $this->renderSeparator('middle'); + } + } + + return $content; + } + + protected function getStyle($style) + { + if ($this->format[$this->style]) { + $style = $this->format[$this->style][$style]; + } else { + $style = [' ', ' ', ' ', ' ']; + } + + return $style; + } + + /** + * 输出表格 + * @access public + * @param array $dataList 表格数据 + * @return string + */ + public function render($dataList = []) + { + if ($dataList) { + $this->setRows($dataList); + } + + // 输出头部 + $content = $this->renderHeader(); + $style = $this->getStyle('cell'); + + if ($this->rows) { + foreach ($this->rows as $row) { + if (is_string($row) && '-' === $row) { + $content .= $this->renderSeparator('middle'); + } elseif (is_scalar($row)) { + $content .= $this->renderSeparator('cross-top'); + $array = str_pad($row, 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) { + return $a + $b; + })); + + $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL; + $content .= $this->renderSeparator('cross-bottom'); + } else { + $array = []; + + foreach ($row as $key => $val) { + $array[] = ' ' . str_pad($val, $this->colWidth[$key], ' ', $this->cellAlign); + } + + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + + } + } + } + + $content .= $this->renderSeparator('bottom'); + + return $content; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/bin/README.md b/Server/application/common/Server/thinkphp/library/think/console/bin/README.md new file mode 100644 index 00000000..9acc52fb --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/Server/application/common/Server/thinkphp/library/think/console/bin/hiddeninput.exe b/Server/application/common/Server/thinkphp/library/think/console/bin/hiddeninput.exe new file mode 100644 index 00000000..c8cf65e8 Binary files /dev/null and b/Server/application/common/Server/thinkphp/library/think/console/bin/hiddeninput.exe differ diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/Build.php b/Server/application/common/Server/thinkphp/library/think/console/command/Build.php new file mode 100644 index 00000000..88a5bf82 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/Build.php @@ -0,0 +1,59 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; +use think\facade\Build as AppBuild; + +class Build extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->setDefinition([ + new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"), + new Option('module', null, Option::VALUE_OPTIONAL, "module name"), + ]) + ->setDescription('Build Application Dirs'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->hasOption('module')) { + AppBuild::module($input->getOption('module')); + $output->writeln("Successed"); + return; + } + + if ($input->hasOption('config')) { + $build = include $input->getOption('config'); + } else { + $build = include App::getAppPath() . 'build.php'; + } + + if (empty($build)) { + $output->writeln("Build Config Is Empty"); + return; + } + + AppBuild::run($build); + $output->writeln("Successed"); + + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/Clear.php b/Server/application/common/Server/thinkphp/library/think/console/command/Clear.php new file mode 100644 index 00000000..14425759 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/Clear.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; +use think\facade\Cache; + +class Clear extends Command +{ + protected function configure() + { + // 指令配置 + $this + ->setName('clear') + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('route', 'u', Option::VALUE_NONE, 'clear route cache') + ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') + ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getOption('route')) { + Cache::clear('route_cache'); + } else { + if ($input->getOption('cache')) { + $path = App::getRuntimePath() . 'cache'; + } elseif ($input->getOption('log')) { + $path = App::getRuntimePath() . 'log'; + } else { + $path = $input->getOption('path') ?: App::getRuntimePath(); + } + + $rmdir = $input->getOption('dir') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); + } + + $output->writeln("Clear Successed"); + } + + protected function clear($path, $rmdir) + { + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); + if ($rmdir) { + rmdir($path . $file); + } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/Help.php b/Server/application/common/Server/thinkphp/library/think/console/command/Help.php new file mode 100644 index 00000000..f1b63b42 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/Help.php @@ -0,0 +1,68 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Help extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this->setName('help')->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ])->setDescription('Displays help for a command')->setHelp(<<%command.name% command displays help for a given command: + + php %command.full_name% list + +To display the list of available commands, please use the list command. +EOF + ); + } + + /** + * Sets the command. + * @param Command $command The command to set + */ + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + if (null === $this->command) { + $this->command = $this->getConsole()->find($input->getArgument('command_name')); + } + + $output->describe($this->command, [ + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/Lists.php b/Server/application/common/Server/thinkphp/library/think/console/command/Lists.php new file mode 100644 index 00000000..6eb856c2 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/Lists.php @@ -0,0 +1,73 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Lists extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<%command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ); + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + $output->describe($this->getConsole(), [ + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + ]); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/Make.php b/Server/application/common/Server/thinkphp/library/think/console/command/Make.php new file mode 100644 index 00000000..2f20954a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/Make.php @@ -0,0 +1,110 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\facade\App; +use think\facade\Config; +use think\facade\Env; + +abstract class Make extends Command +{ + protected $type; + + abstract protected function getStub(); + + protected function configure() + { + $this->addArgument('name', Argument::REQUIRED, "The name of the class"); + } + + protected function execute(Input $input, Output $output) + { + + $name = trim($input->getArgument('name')); + + $classname = $this->getClassName($name); + + $pathname = $this->getPathName($classname); + + if (is_file($pathname)) { + $output->writeln('' . $this->type . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(dirname($pathname), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ' created successfully.'); + + } + + protected function buildClass($name) + { + $stub = file_get_contents($this->getStub()); + + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + + return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + Config::get('action_suffix'), + $namespace, + App::getNamespace(), + ], $stub); + } + + protected function getPathName($name) + { + $name = str_replace(App::getNamespace() . '\\', '', $name); + + return Env::get('app_path') . ltrim(str_replace('\\', '/', $name), '/') . '.php'; + } + + protected function getClassName($name) + { + $appNamespace = App::getNamespace(); + + if (strpos($name, $appNamespace . '\\') !== false) { + return $name; + } + + if (Config::get('app_multi_module')) { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = 'common'; + } + } else { + $module = null; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($appNamespace, $module) . '\\' . $name; + } + + protected function getNamespace($appNamespace, $module) + { + return $module ? ($appNamespace . '\\' . $module) : $appNamespace; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/RouteList.php b/Server/application/common/Server/thinkphp/library/think/console/command/RouteList.php new file mode 100644 index 00000000..0405c31b --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/RouteList.php @@ -0,0 +1,130 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\console\Table; +use think\Container; + +class RouteList extends Command +{ + protected $sortBy = [ + 'rule' => 0, + 'route' => 1, + 'method' => 2, + 'name' => 3, + 'domain' => 4, + ]; + + protected function configure() + { + $this->setName('route:list') + ->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default') + ->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0) + ->addOption('more', 'm', Option::VALUE_NONE, 'show route options.') + ->setDescription('show route list.'); + } + + protected function execute(Input $input, Output $output) + { + $filename = Container::get('app')->getRuntimePath() . 'route_list.php'; + + if (is_file($filename)) { + unlink($filename); + } + + $content = $this->getRouteList(); + file_put_contents($filename, 'Route List' . PHP_EOL . $content); + } + + protected function getRouteList() + { + Container::get('route')->setTestMode(true); + // 路由检测 + $path = Container::get('app')->getRoutePath(); + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + + if (is_array($rules)) { + Container::get('route')->import($rules); + } + } + } + + if (Container::get('config')->get('route_annotation')) { + $suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix'); + + include Container::get('build')->buildRoute($suffix); + } + + $table = new Table(); + + if ($this->input->hasOption('more')) { + $header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern']; + } else { + $header = ['Rule', 'Route', 'Method', 'Name', 'Domain']; + } + + $table->setHeader($header); + + $routeList = Container::get('route')->getRuleList(); + $rows = []; + + foreach ($routeList as $domain => $items) { + foreach ($items as $item) { + $item['route'] = $item['route'] instanceof \Closure ? '' : $item['route']; + + if ($this->input->hasOption('more')) { + $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain, json_encode($item['option']), json_encode($item['pattern'])]; + } else { + $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain]; + } + + $rows[] = $item; + } + } + + if ($this->input->getOption('sort')) { + $sort = $this->input->getOption('sort'); + + if (isset($this->sortBy[$sort])) { + $sort = $this->sortBy[$sort]; + } + + uasort($rows, function ($a, $b) use ($sort) { + $itemA = isset($a[$sort]) ? $a[$sort] : null; + $itemB = isset($b[$sort]) ? $b[$sort] : null; + + return strcasecmp($itemA, $itemB); + }); + } + + $table->setRows($rows); + + if ($this->input->getArgument('style')) { + $style = $this->input->getArgument('style'); + $table->setStyle($style); + } + + return $this->table($table); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/RunServer.php b/Server/application/common/Server/thinkphp/library/think/console/command/RunServer.php new file mode 100644 index 00000000..2e028dc6 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/RunServer.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; + +class RunServer extends Command +{ + public function configure() + { + $this->setName('run') + ->addOption('host', 'H', Option::VALUE_OPTIONAL, + 'The host to server the application on', '127.0.0.1') + ->addOption('port', 'p', Option::VALUE_OPTIONAL, + 'The port to server the application on', 8000) + ->addOption('root', 'r', Option::VALUE_OPTIONAL, + 'The document root of the application', App::getRootPath() . 'public') + ->setDescription('PHP Built-in Server for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + $host = $input->getOption('host'); + $port = $input->getOption('port'); + $root = $input->getOption('root'); + + $command = sprintf( + 'php -S %s:%d -t %s %s', + $host, + $port, + escapeshellarg($root), + escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php') + ); + + $output->writeln(sprintf('ThinkPHP Development server is started On ', $host, $port)); + $output->writeln(sprintf('You can exit with `CTRL-C`')); + $output->writeln(sprintf('Document root is: %s', $root)); + passthru($command); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/Version.php b/Server/application/common/Server/thinkphp/library/think/console/command/Version.php new file mode 100644 index 00000000..ee7eca9c --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/Version.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\facade\App; + +class Version extends Command +{ + protected function configure() + { + // 指令配置 + $this->setName('version') + ->setDescription('show thinkphp framework version'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('v' . App::version()); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/make/Command.php b/Server/application/common/Server/thinkphp/library/think/console/command/make/Command.php new file mode 100644 index 00000000..b539eb23 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/make/Command.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Argument; +use think\facade\App; + +class Command extends Make +{ + protected $type = "Command"; + + protected function configure() + { + parent::configure(); + $this->setName('make:command') + ->addArgument('commandName', Argument::OPTIONAL, "The name of the command") + ->setDescription('Create a new command class'); + } + + protected function buildClass($name) + { + $commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name)); + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + $stub = file_get_contents($this->getStub()); + + return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + $commandName, + $class, + $namespace, + App::getNamespace(), + ], $stub); + } + + protected function getStub() + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return $appNamespace . '\\command'; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/make/Controller.php b/Server/application/common/Server/thinkphp/library/think/console/command/make/Controller.php new file mode 100644 index 00000000..2a6ab770 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/make/Controller.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Option; +use think\facade\Config; + +class Controller extends Make +{ + protected $type = "Controller"; + + protected function configure() + { + parent::configure(); + $this->setName('make:controller') + ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.') + ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub() + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + if ($this->input->getOption('api')) { + return $stubPath . 'controller.api.stub'; + } + + if ($this->input->getOption('plain')) { + return $stubPath . 'controller.plain.stub'; + } + + return $stubPath . 'controller.stub'; + } + + protected function getClassName($name) + { + return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\controller'; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/make/Middleware.php b/Server/application/common/Server/thinkphp/library/think/console/command/make/Middleware.php new file mode 100644 index 00000000..bfe821b0 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/make/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Middleware extends Make +{ + protected $type = "Middleware"; + + protected function configure() + { + parent::configure(); + $this->setName('make:middleware') + ->setDescription('Create a new middleware class'); + } + + protected function getStub() + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, 'http') . '\middleware'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/make/Model.php b/Server/application/common/Server/thinkphp/library/think/console/command/make/Model.php new file mode 100644 index 00000000..03e6b3fc --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/make/Model.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Model extends Make +{ + protected $type = "Model"; + + protected function configure() + { + parent::configure(); + $this->setName('make:model') + ->setDescription('Create a new model class'); + } + + protected function getStub() + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\model'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/make/Validate.php b/Server/application/common/Server/thinkphp/library/think/console/command/make/Validate.php new file mode 100644 index 00000000..89830ad1 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/make/Validate.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Validate extends Make +{ + protected $type = "Validate"; + + protected function configure() + { + parent::configure(); + $this->setName('make:validate') + ->setDescription('Create a validate class'); + } + + protected function getStub() + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + return $stubPath . 'validate.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\validate'; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/make/stubs/command.stub b/Server/application/common/Server/thinkphp/library/think/console/command/make/stubs/command.stub new file mode 100644 index 00000000..d2c7c1e7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/make/stubs/command.stub @@ -0,0 +1,24 @@ +setName('{%commandName%}'); + // 设置参数 + + } + + protected function execute(Input $input, Output $output) + { + // 指令输出 + $output->writeln('{%commandName%}'); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/make/stubs/controller.api.stub b/Server/application/common/Server/thinkphp/library/think/console/command/make/stubs/controller.api.stub new file mode 100644 index 00000000..54ec0594 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/make/stubs/controller.api.stub @@ -0,0 +1,64 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = []; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = []; +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Autoload.php b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Autoload.php new file mode 100644 index 00000000..b51fd259 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Autoload.php @@ -0,0 +1,279 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\Container; + +class Autoload extends Command +{ + protected function configure() + { + $this->setName('optimize:autoload') + ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); + } + + protected function execute(Input $input, Output $output) + { + + $classmapFile = <<getNamespace() . '\\' => realpath(rtrim($app->getAppPath())), + 'think\\' => $app->getThinkPath() . 'library/think', + 'traits\\' => $app->getThinkPath() . 'library/traits', + '' => realpath(rtrim($app->getRootPath() . 'extend')), + ]; + + krsort($namespacesToScan); + $classMap = []; + foreach ($namespacesToScan as $namespace => $dir) { + + if (!is_dir($dir)) { + continue; + } + + $namespaceFilter = '' === $namespace ? null : $namespace; + $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); + } + + ksort($classMap); + foreach ($classMap as $class => $code) { + $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; + } + $classmapFile .= "];\n"; + $runtimePath = $app->getRuntimePath(); + if (!is_dir($runtimePath)) { + @mkdir($runtimePath, 0755, true); + } + + file_put_contents($runtimePath . 'classmap.php', $classmapFile); + + $output->writeln('Succeed!'); + } + + protected function addClassMapCode($dir, $namespace, $classMap) + { + foreach ($this->createMap($dir, $namespace) as $class => $path) { + + $pathCode = $this->getPathCode($path) . ",\n"; + + if (!isset($classMap[$class])) { + $classMap[$class] = $pathCode; + } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . str_replace(["',\n"], [ + '', + ], $classMap[$class]) . '" and "' . $path . '", the first will be used.' + ); + } + } + return $classMap; + } + + protected function getPathCode($path) + { + $baseDir = ''; + $app = Container::get('app'); + $appPath = $this->normalizePath(realpath($app->getAppPath())); + $libPath = $this->normalizePath(realpath($app->getThinkPath() . 'library')); + $extendPath = $this->normalizePath(realpath($app->getRootPath() . 'extend')); + $path = $this->normalizePath($path); + + if (strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen($app->getThinkPath() . 'library')); + $baseDir = "'" . $libPath . "/'"; + } elseif (strpos($path, $appPath . '/') === 0) { + $path = substr($path, strlen($appPath) + 1); + $baseDir = "'" . $appPath . "/'"; + } elseif (strpos($path, $extendPath . '/') === 0) { + $path = substr($path, strlen($extendPath) + 1); + $baseDir = "'" . $extendPath . "/'"; + } + + if (false !== $path) { + $baseDir .= " . "; + } + + return $baseDir . ((false !== $path) ? var_export($path, true) : ""); + } + + protected function normalizePath($path) + { + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = false; + + if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + if (substr($path, 0, 1) === '/') { + $absolute = true; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && ($absolute || $up)) { + array_pop($parts); + $up = !(empty($parts) || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + return $prefix . ($absolute ? '/' : '') . implode('/', $parts); + } + + protected function createMap($path, $namespace = null) + { + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path)) { + + $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); + + $path = []; + + /** @var \SplFileInfo $object */ + foreach ($objects as $object) { + if ($object->isFile() && $object->getExtension() == 'php') { + $path[] = $object; + } + } + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "' . $path . + '" which does not appear to be a file nor a folder' + ); + } + } + + $map = []; + + /** @var \SplFileInfo $file */ + foreach ($path as $file) { + $filePath = $file->getRealPath(); + + if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { + continue; + } + + $classes = $this->findClasses($filePath); + + foreach ($classes as $class) { + if (null !== $namespace && 0 !== strpos($class, $namespace)) { + continue; + } + + if (!isset($map[$class])) { + $map[$class] = $filePath; + } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' + ); + } + } + } + + return $map; + } + + protected function findClasses($path) + { + $extraTypes = '|trait'; + + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!is_readable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim(file_get_contents($path))) { + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { + return []; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); + if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + if (':' === $name[0]) { + $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif ('enum' === $matches['type'][$i]) { + $name = rtrim($name, ':'); + } + $classes[] = ltrim($namespace . $name, '\\'); + } + } + + return $classes; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Config.php b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Config.php new file mode 100644 index 00000000..da955568 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Config.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\Container; +use think\facade\App; + +class Config extends Command +{ + protected function configure() + { + $this->setName('optimize:config') + ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') + ->setDescription('Build config and common file cache.'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getArgument('module')) { + $module = $input->getArgument('module') . DIRECTORY_SEPARATOR; + } else { + $module = ''; + } + + $content = 'buildCacheContent($module); + $runtimePath = App::getRuntimePath(); + if (!is_dir($runtimePath . $module)) { + @mkdir($runtimePath . $module, 0755, true); + } + + file_put_contents($runtimePath . $module . 'init.php', $content); + + $output->writeln('Succeed!'); + } + + protected function buildCacheContent($module) + { + $content = '// This cache file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL; + $path = realpath(App::getAppPath() . $module) . DIRECTORY_SEPARATOR; + if ($module) { + $configPath = is_dir($path . 'config') ? $path . 'config' : App::getConfigPath() . $module; + } else { + $configPath = App::getConfigPath(); + } + $ext = App::getConfigExt(); + $config = Container::get('config'); + + $files = is_dir($configPath) ? scandir($configPath) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $ext) { + $filename = $configPath . DIRECTORY_SEPARATOR . $file; + $config->load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + + // 加载行为扩展文件 + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export($tags, true)) . ');' . PHP_EOL; + } + } + + // 加载公共文件 + if (is_file($path . 'common.php')) { + $common = substr(php_strip_whitespace($path . 'common.php'), 6); + if ($common) { + $content .= PHP_EOL . $common . PHP_EOL; + } + } + + if ('' == $module) { + $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL; + + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $content .= PHP_EOL . '\think\Container::get("middleware")->import(' . var_export($middleware, true) . ');' . PHP_EOL; + } + } + } + + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $content .= PHP_EOL . '\think\Container::getInstance()->bindTo(' . var_export($provider, true) . ');' . PHP_EOL; + } + } + + $content .= PHP_EOL . '\think\facade\Config::set(' . var_export($config->get(), true) . ');' . PHP_EOL; + + return $content; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Route.php b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Route.php new file mode 100644 index 00000000..f6dc6328 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Route.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\Container; + +class Route extends Command +{ + protected function configure() + { + $this->setName('optimize:route') + ->setDescription('Build route cache.'); + } + + protected function execute(Input $input, Output $output) + { + $filename = Container::get('app')->getRuntimePath() . 'route.php'; + if (is_file($filename)) { + unlink($filename); + } + file_put_contents($filename, $this->buildRouteCache()); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache() + { + Container::get('route')->setName([]); + Container::get('route')->setTestMode(true); + // 路由检测 + $path = Container::get('app')->getRoutePath(); + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + Container::get('route')->import($rules); + } + } + } + + if (Container::get('config')->get('route_annotation')) { + $suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix'); + include Container::get('build')->buildRoute($suffix); + } + + $content = 'getName(), true) . ';'; + return $content; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Schema.php b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Schema.php new file mode 100644 index 00000000..16ac83d5 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/command/optimize/Schema.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\Db; +use think\facade\App; + +class Schema extends Command +{ + protected function configure() + { + $this->setName('optimize:schema') + ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + if (!is_dir(App::getRuntimePath() . 'schema')) { + @mkdir(App::getRuntimePath() . 'schema', 0755, true); + } + + if ($input->hasOption('module')) { + $module = $input->getOption('module'); + // 读取模型 + $path = App::getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $namespace = App::getNamespace(); + + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $namespace . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + + $output->writeln('Succeed!'); + return; + } elseif ($input->hasOption('table')) { + $table = $input->getOption('table'); + if (false === strpos($table, '.')) { + $dbName = Db::getConfig('database'); + } + + $tables[] = $table; + } elseif ($input->hasOption('db')) { + $dbName = $input->getOption('db'); + $tables = Db::getConnection()->getTables($dbName); + } elseif (!\think\facade\Config::get('app_multi_module')) { + $namespace = App::getNamespace(); + $path = App::getAppPath() . 'model'; + $list = is_dir($path) ? scandir($path) : []; + + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + + $output->writeln('Succeed!'); + return; + } else { + $tables = Db::getConnection()->getTables(); + } + + $db = isset($dbName) ? $dbName . '.' : ''; + $this->buildDataBaseSchema($tables, $db); + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema($class) + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + $table = $class::getTable(); + $dbName = $class::getConfig('database'); + $content = 'getFields($table); + $content .= var_export($info, true) . ';'; + + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . '.' . $table . '.php', $content); + } + } + + protected function buildDataBaseSchema($tables, $db) + { + if ('' == $db) { + $dbName = Db::getConfig('database') . '.'; + } else { + $dbName = $db; + } + + foreach ($tables as $table) { + $content = 'getFields($db . $table); + $content .= var_export($info, true) . ';'; + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . $table . '.php', $content); + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/input/Argument.php b/Server/application/common/Server/thinkphp/library/think/console/input/Argument.php new file mode 100644 index 00000000..16223bbe --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/input/Argument.php @@ -0,0 +1,115 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Argument +{ + + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 参数名 + * @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL + * @param string $description 描述 + * @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效) + * @throws \InvalidArgumentException + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * 获取参数名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/input/Definition.php b/Server/application/common/Server/thinkphp/library/think/console/input/Definition.php new file mode 100644 index 00000000..c71977ec --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/input/Definition.php @@ -0,0 +1,375 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Definition +{ + + /** + * @var Argument[] + */ + private $arguments; + + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + + /** + * @var Option[] + */ + private $options; + private $shortcuts; + + /** + * 构造方法 + * @param array $definition + * @api + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * 设置指令的定义 + * @param array $definition 定义的数组 + */ + public function setDefinition(array $definition) + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof Option) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * 设置参数 + * @param Argument[] $arguments 参数数组 + */ + public function setArguments($arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments($arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * 根据名称或者位置获取参数 + * @param string|int $name 参数名或者位置 + * @return Argument 参数 + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * 根据名称或位置检查是否具有某个参数 + * @param string|int $name 参数名或者位置 + * @return bool + * @api + */ + public function hasArgument($name) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions($options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions($options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) + && !$option->equals($this->options[$this->shortcuts[$shortcut]]) + ) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * 根据名称获取选项 + * @param string $name 选项名 + * @return Option + * @throws \InvalidArgumentException + * @api + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * 根据名称检查是否有这个选项 + * @param string $name 选项名 + * @return bool + * @api + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * 获取该指令的介绍 + * @param bool $short 是否简洁介绍 + * @return string + */ + public function getSynopsis($short = false) + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<' . $argument->getName() . '>'; + if (!$argument->isRequired()) { + $element = '[' . $element . ']'; + } elseif ($argument->isArray()) { + $element .= ' (' . $element . ')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/input/Option.php b/Server/application/common/Server/thinkphp/library/think/console/input/Option.php new file mode 100644 index 00000000..e5707c9a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/input/Option.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Option +{ + + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 选项名 + * @param string|array $shortcut 短名称,多个用|隔开或者使用数组 + * @param int $mode 选项类型(可选类型为 self::VALUE_*) + * @param string $description 描述 + * @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null) + * @throws \InvalidArgumentException + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new \InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new \InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * 获取短名称 + * @return string + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * 获取选项名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否可以设置值 + * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * 是否必须 + * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * 是否可选 + * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * 选项值是否接受数组 + * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述文字 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 检查所给选项是否是当前这个 + * @param Option $option + * @return bool + */ + public function equals(Option $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional(); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/Ask.php b/Server/application/common/Server/thinkphp/library/think/console/output/Ask.php new file mode 100644 index 00000000..3933eb29 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/Ask.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\console\Input; +use think\console\Output; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +class Ask +{ + private static $stty; + + private static $shell; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var Question */ + protected $question; + + public function __construct(Input $input, Output $output, Question $question) + { + $this->input = $input; + $this->output = $output; + $this->question = $question; + } + + public function run() + { + if (!$this->input->isInteractive()) { + return $this->question->getDefault(); + } + + if (!$this->question->getValidator()) { + return $this->doAsk(); + } + + $that = $this; + + $interviewer = function () use ($that) { + return $that->doAsk(); + }; + + return $this->validateAttempts($interviewer); + } + + protected function doAsk() + { + $this->writePrompt(); + + $inputStream = STDIN; + $autocomplete = $this->question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($this->question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($inputStream)); + } catch (\RuntimeException $e) { + if (!$this->question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); + + if ($normalizer = $this->question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function autocomplete($inputStream) + { + $autocomplete = $this->question->getAutocompleterValues(); + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -icanon -echo'); + + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + $this->output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + $c .= fread($inputStream, 2); + + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + $this->output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $this->output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $this->output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + $this->output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + $this->output->write("\0337"); + $this->output->highlight(substr($matches[$ofs], $i)); + $this->output->write("\0338"); + } + } + + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + protected function getHiddenResponse($inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__ . '/../bin/hiddeninput.exe'; + + $value = rtrim(shell_exec($exe)); + $this->output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $this->output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $this->output->writeln(''); + + return $value; + } + + throw new \RuntimeException('Unable to hide the response.'); + } + + protected function validateAttempts($interviewer) + { + /** @var \Exception $error */ + $error = null; + $attempts = $this->question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->output->error($error->getMessage()); + } + + try { + return call_user_func($this->question->getValidator(), $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * 显示问题的提示信息 + */ + protected function writePrompt() + { + $text = $this->question->getQuestion(); + $default = $this->question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $this->question instanceof Confirmation: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $this->question instanceof Choice && $this->question->isMultiselect(): + $choices = $this->question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, implode(', ', $default)); + + break; + + case $this->question instanceof Choice: + $choices = $this->question->getChoices(); + $text = sprintf(' %s [%s]:', $text, $choices[$default]); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, $default); + } + + $this->output->writeln($text); + + if ($this->question instanceof Choice) { + $width = max(array_map('strlen', array_keys($this->question->getChoices()))); + + foreach ($this->question->getChoices() as $key => $value) { + $this->output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $this->output->write(' > '); + } + + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/Descriptor.php b/Server/application/common/Server/thinkphp/library/think/console/output/Descriptor.php new file mode 100644 index 00000000..6d98d53c --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/Descriptor.php @@ -0,0 +1,319 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\Console; +use think\console\Command; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\descriptor\Console as ConsoleDescription; + +class Descriptor +{ + + /** + * @var Output + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(Output $output, $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Console: + $this->describeConsole($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * 输出内容 + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW); + } + + /** + * 描述参数 + * @param InputArgument $argument + * @param array $options + * @return string|mixed + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() + && (!is_array($argument->getDefault()) + || count($argument->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; + + $this->writeText(sprintf(" %s%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options); + } + + /** + * 描述选项 + * @param InputOption $option + * @param array $options + * @return string|mixed + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() + && (!is_array($option->getDefault()) + || count($option->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '=' . strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '[' . $value . ']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value)); + + $spacingWidth = $totalWidth - strlen($synopsis) + 2; + + $this->writeText(sprintf(" %s%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : ''), $options); + } + + /** + * 描述输入 + * @param InputDefinition $definition + * @param array $options + * @return string|mixed + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + /** + * 描述指令 + * @param Command $command + * @param array $options + * @return string|mixed + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeConsoleDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' ' . $usage, $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' ' . str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * 描述控制台 + * @param Console $console + * @param array $options + * @return string|mixed + */ + protected function describeConsole(Console $console, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ConsoleDescription($console, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $console->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' ' . $namespace['id'] . '', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - strlen($name); + $this->writeText(sprintf(" %s%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name) + ->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = []) + { + $this->write(isset($options['raw_text']) + && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true); + } + + /** + * 格式化 + * @param mixed $default + * @return string + */ + private function formatDefaultValue($default) + { + return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * @param Command[] $commands + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + return $width + 2; + } + + /** + * @param InputOption[] $options + * @return int + */ + private function calculateTotalWidthForOptions($options) + { + $totalWidth = 0; + foreach ($options as $option) { + $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- + + if ($option->acceptValue()) { + $valueLength = 1 + strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/Formatter.php b/Server/application/common/Server/thinkphp/library/think/console/output/Formatter.php new file mode 100644 index 00000000..f8bee552 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/Formatter.php @@ -0,0 +1,198 @@ + +// +---------------------------------------------------------------------- +namespace think\console\output; + +use think\console\output\formatter\Stack as StyleStack; +use think\console\output\formatter\Style; + +class Formatter +{ + + private $decorated = false; + private $styles = []; + private $styleStack; + + /** + * 转义 + * @param string $text + * @return string + */ + public static function escape($text) + { + return preg_replace('/([^\\\\]?)setStyle('error', new Style('white', 'red')); + $this->setStyle('info', new Style('green')); + $this->setStyle('comment', new Style('yellow')); + $this->setStyle('question', new Style('black', 'cyan')); + $this->setStyle('highlight', new Style('red')); + $this->setStyle('warning', new Style('black', 'yellow')); + + $this->styleStack = new StyleStack(); + } + + /** + * 设置外观标识 + * @param bool $decorated 是否美化文字 + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * 获取外观标识 + * @return bool + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * 添加一个新样式 + * @param string $name 样式名 + * @param Style $style 样式实例 + */ + public function setStyle($name, Style $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * 是否有这个样式 + * @param string $name + * @return bool + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * 获取样式 + * @param string $name + * @return Style + * @throws \InvalidArgumentException + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * 使用所给的样式格式化文字 + * @param string $message 文字 + * @return string + */ + public function format($message) + { + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + return str_replace('\\<', '<', $output); + } + + /** + * @return StyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * 根据字符串创建新的样式实例 + * @param string $string + * @return Style|bool + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new Style(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * 从堆栈应用样式到文字 + * @param string $text 文字 + * @return string + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/Question.php b/Server/application/common/Server/thinkphp/library/think/console/output/Question.php new file mode 100644 index 00000000..03975f27 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/Question.php @@ -0,0 +1,211 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +class Question +{ + + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * 构造方法 + * @param string $question 问题 + * @param mixed $default 默认答案 + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * 获取问题 + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * 获取默认答案 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 是否隐藏答案 + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * 隐藏答案 + * @param bool $hidden + * @return Question + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * 不能被隐藏是否撤销 + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * 设置不能被隐藏的时候的操作 + * @param bool $fallback + * @return Question + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * 获取自动完成 + * @return null|array|\Traversable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * 设置自动完成的值 + * @param null|array|\Traversable $values + * @return Question + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values) && $this->isAssoc($values)) { + $values = array_merge(array_keys($values), array_values($values)); + } + + if (null !== $values && !is_array($values)) { + if (!$values instanceof \Traversable || $values instanceof \Countable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); + } + } + + if ($this->hidden) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * 设置答案的验证器 + * @param null|callable $validator + * @return Question The current instance + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * 获取验证器 + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * 设置最大重试次数 + * @param null|int $attempts + * @return Question + * @throws \InvalidArgumentException + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * 获取最大重试次数 + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * 设置响应的回调 + * @param string|\Closure $normalizer + * @return Question + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * 获取响应回调 + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * @return string|\Closure + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/descriptor/Console.php b/Server/application/common/Server/thinkphp/library/think/console/output/descriptor/Console.php new file mode 100644 index 00000000..8739c536 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/descriptor/Console.php @@ -0,0 +1,153 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\descriptor; + +use think\Console as ThinkConsole; +use think\console\Command; + +class Console +{ + + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var ThinkConsole + */ + private $console; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * 构造方法 + * @param ThinkConsole $console + * @param string|null $namespace + */ + public function __construct(ThinkConsole $console, $namespace = null) + { + $this->console = $console; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectConsole() + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (is_string($command)) { + $command = new $command(); + } + + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + /** + * @param array $commands + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->console->extractNamespace($name, 1); + if (!$key) { + $key = self::GLOBAL_NAMESPACE; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/driver/Buffer.php b/Server/application/common/Server/thinkphp/library/think/console/output/driver/Buffer.php new file mode 100644 index 00000000..c77a2ec4 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/driver/Buffer.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Buffer +{ + /** + * @var string + */ + private $buffer = ''; + + public function __construct(Output $output) + { + // do nothing + } + + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + return $content; + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Exception $e) + { + // do nothing + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/driver/Console.php b/Server/application/common/Server/thinkphp/library/think/console/output/driver/Console.php new file mode 100644 index 00000000..e041b525 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/driver/Console.php @@ -0,0 +1,368 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; +use think\console\output\Formatter; + +class Console +{ + + /** @var Resource */ + private $stdout; + + /** @var Formatter */ + private $formatter; + + private $terminalDimensions; + + /** @var Output */ + private $output; + + public function __construct(Output $output) + { + $this->output = $output; + $this->formatter = new Formatter(); + $this->stdout = $this->openOutputStream(); + $decorated = $this->hasColorSupport($this->stdout); + $this->formatter->setDecorated($decorated); + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null) + { + if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case Output::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case Output::OUTPUT_RAW: + break; + case Output::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline, $stream); + } + } + + public function renderException(\Exception $e) + { + $stderr = $this->openErrorStream(); + $decorated = $this->hasColorSupport($stderr); + $this->formatter->setDecorated($decorated); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = []; + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = ['', '']; + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); + + if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { + $this->write('Exception trace:', true, Output::OUTPUT_NORMAL, $stderr); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); + } + + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + } + } while ($e = $e->getPrevious()); + + } + + /** + * 获取终端宽度 + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * 获取终端高度 + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * 获取当前终端的尺寸 + * @return array + */ + public function getTerminalDimensions() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + } + + if ($sttyString = $this->getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + } + + return [null, null]; + } + + /** + * 获取stty列数 + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + return; + } + + /** + * 获取终端模式 + * @return string x 或 null + */ + private function getMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2] . 'x' . $matches[1]; + } + } + return; + } + + private function stringWidth($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth($string, $width) + { + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + if (mb_strwidth($line . $char, 'utf8') <= $width) { + $line .= $char; + continue; + } + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + private function isRunningOS400() + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } + + /** + * 将消息写入到输出。 + * @param string $message 消息 + * @param bool $newline 是否另起一行 + * @param null $stream + */ + protected function doWrite($message, $newline, $stream = null) + { + if (null === $stream) { + $stream = $this->stdout; + } + if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { + throw new \RuntimeException('Unable to write output.'); + } + + fflush($stream); + } + + /** + * 是否支持着色 + * @param $stream + * @return bool + */ + protected function hasColorSupport($stream) + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($stream); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/driver/Nothing.php b/Server/application/common/Server/thinkphp/library/think/console/output/driver/Nothing.php new file mode 100644 index 00000000..9a55f777 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/driver/Nothing.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + // do nothing + } + + public function renderException(\Exception $e) + { + // do nothing + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/formatter/Stack.php b/Server/application/common/Server/thinkphp/library/think/console/output/formatter/Stack.php new file mode 100644 index 00000000..4864a3f2 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/formatter/Stack.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Stack +{ + + /** + * @var Style[] + */ + private $styles; + + /** + * @var Style + */ + private $emptyStyle; + + /** + * 构造方法 + * @param Style|null $emptyStyle + */ + public function __construct(Style $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new Style(); + $this->reset(); + } + + /** + * 重置堆栈 + */ + public function reset() + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style) + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + /** + * @var int $index + * @var Style $stackedStyle + */ + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * 计算堆栈的当前样式。 + * @return Style + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @param Style $emptyStyle + * @return Stack + */ + public function setEmptyStyle(Style $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return Style + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/formatter/Style.php b/Server/application/common/Server/thinkphp/library/think/console/output/formatter/Style.php new file mode 100644 index 00000000..d9b09998 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/formatter/Style.php @@ -0,0 +1,189 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + + private static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + ]; + private static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + ]; + private static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $options = []; + + /** + * 初始化输出的样式 + * @param string|null $foreground 字体颜色 + * @param string|null $background 背景色 + * @param array $options 格式 + * @api + */ + public function __construct($foreground = null, $background = null, array $options = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * 设置字体颜色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * 设置背景色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * 设置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException When the option name isn't defined + * @api + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * 重置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * 批量设置字体格式 + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * 应用样式到文字 + * @param string $text 文字 + * @return string + */ + public function apply($text) + { + $setCodes = []; + $unsetCodes = []; + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/question/Choice.php b/Server/application/common/Server/thinkphp/library/think/console/output/question/Choice.php new file mode 100644 index 00000000..cdc3b4e4 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/question/Choice.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Choice extends Question +{ + + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * 构造方法 + * @param string $question 问题 + * @param array $choices 选项 + * @param mixed $default 默认答案 + */ + public function __construct($question, array $choices, $default = null) + { + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * 可选项 + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * 获取默认的验证方法 + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = [$selected]; + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (!empty($result)) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (empty($result) && array_key_exists($value, $choices)) { + $result = $value; + } + + if (false === $result) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/console/output/question/Confirmation.php b/Server/application/common/Server/thinkphp/library/think/console/output/question/Confirmation.php new file mode 100644 index 00000000..6598f9b3 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/console/output/question/Confirmation.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Confirmation extends Question +{ + + private $trueAnswerRegex; + + /** + * 构造方法 + * @param string $question 问题 + * @param bool $default 默认答案 + * @param string $trueAnswerRegex 验证正则 + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * 获取默认的答案回调 + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/Builder.php b/Server/application/common/Server/thinkphp/library/think/db/Builder.php new file mode 100644 index 00000000..60b470e8 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/Builder.php @@ -0,0 +1,1173 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Exception; + +abstract class Builder +{ + // connection对象实例 + protected $connection; + + // 查询表达式映射 + protected $exp = ['EQ' => '=', 'NEQ' => '<>', 'GT' => '>', 'EGT' => '>=', 'LT' => '<', 'ELT' => '<=', 'NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + // SQL表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + protected $updateSql = 'UPDATE %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $deleteSql = 'DELETE FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 架构函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 注册查询表达式解析 + * @access public + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this + */ + public function bindParser($name, $parser) + { + $this->parser[$name] = $parser; + return $this; + } + + /** + * 数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 + * @return array + */ + protected function parseData(Query $query, $data = [], $fields = [], $bind = []) + { + if (empty($data)) { + return []; + } + + $options = $query->getOptions(); + + // 获取绑定信息 + if (empty($bind)) { + $bind = $this->connection->getFieldsBind($options['table']); + } + + if (empty($fields)) { + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + } + + $result = []; + + foreach ($data as $key => $val) { + if ('*' != $options['field'] && !in_array($key, $fields, true)) { + continue; + } + + $item = $this->parseKey($query, $key, true); + + if ($val instanceof Expression) { + $result[$item] = $val->getValue(); + continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) { + $val = json_encode($val, JSON_UNESCAPED_UNICODE); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $val = $val->__toString(); + } + + if (false !== strpos($key, '->')) { + list($key, $name) = explode('->', $key); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind) . ')'; + } elseif ('*' == $options['field'] && false === strpos($key, '.') && !in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } elseif (is_array($val) && !empty($val)) { + switch (strtoupper($val[0])) { + case 'INC': + $result[$item] = $item . ' + ' . floatval($val[1]); + break; + case 'DEC': + $result[$item] = $item . ' - ' . floatval($val[1]); + break; + case 'EXP': + throw new Exception('not support data:[' . $val[0] . ']'); + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + $result[$item] = $this->parseDataBind($query, $key, $val, $bind); + } + } + + return $result; + } + + /** + * 数据绑定处理 + * @access protected + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @return string + */ + protected function parseDataBind(Query $query, $key, $data, $bind = []) + { + if ($data instanceof Expression) { + return $data->getValue(); + } + + $name = $query->bind($data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + + return ':' . $name; + } + + /** + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + return $key instanceof Expression ? $key->getValue() : $key; + } + + /** + * field分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $fields 字段名 + * @return string + */ + protected function parseField(Query $query, $fields) + { + if ('*' == $fields || empty($fields)) { + $fieldsStr = '*'; + } elseif (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + + foreach ($fields as $key => $field) { + if (!is_numeric($key)) { + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); + } else { + $array[] = $this->parseKey($query, $field); + } + } + + $fieldsStr = implode(',', $array); + } + + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $tables 表名 + * @return string + */ + protected function parseTable(Query $query, $tables) + { + $item = []; + $options = $query->getOptions(); + + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + $key = $this->connection->parseSqlTable($key); + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); + } else { + $table = $this->connection->parseSqlTable($table); + + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); + } else { + $item[] = $this->parseKey($query, $table); + } + } + } + + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + protected function parseWhere(Query $query, $where) + { + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $binds = $this->connection->getFieldsBind($options['table']); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, '', $binds); + } + + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + public function buildWhere(Query $query, $where) + { + if (empty($where)) { + $where = []; + } + + $whereStr = ''; + $binds = $this->connection->getFieldsBind($query->getOptions('table')); + + foreach ($where as $logic => $val) { + $str = []; + + foreach ($val as $value) { + if ($value instanceof Expression) { + $str[] = ' ' . $logic . ' ( ' . $value->getValue() . ' )'; + continue; + } + + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof \Closure) { + // 使用闭包查询 + $newQuery = $query->newQuery()->setConnection($this->connection); + $value($newQuery); + $whereClause = $this->buildWhere($newQuery, $newQuery->getOptions('where')); + + if (!empty($whereClause)) { + $query->bind($newQuery->getBind(false)); + $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )'; + } + } elseif (is_array($field)) { + array_unshift($value, $field); + $str2 = []; + foreach ($value as $item) { + $str2[] = $this->parseWhereItem($query, array_shift($item), $item, $logic, $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $str2) . ' )'; + } elseif (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + $item = []; + + foreach ($array as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + $item = []; + + foreach ($array as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $str[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $logic, $binds); + } + } + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + // where子单元分析 + protected function parseWhereItem(Query $query, $field, $val, $rule = '', $binds = []) + { + // 字段分析 + $key = $field ? $this->parseKey($query, $field, true) : ''; + + // 查询规则和条件 + if (!is_array($val)) { + $val = is_null($val) ? ['NULL', ''] : ['=', $val]; + } + + list($exp, $value) = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $item = array_pop($val); + + // 传入 or 或者 and + if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { + $rule = $item; + } else { + array_push($val, $item); + } + + foreach ($val as $k => $item) { + $str[] = $this->parseWhereItem($query, $field, $item, $rule, $binds); + } + + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; + } + + // 检测操作符 + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + + if ($value instanceof Expression) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + if (strpos($field, '->')) { + $jsonType = $query->getJsonFieldType($field); + $bindType = $this->connection->getFieldBindType($jsonType); + } else { + $bindType = isset($binds[$field]) && 'LIKE' != $exp ? $binds[$field] : PDO::PARAM_STR; + } + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (0 === strpos($value, ':') && $query->isBind(substr($value, 1))) { + } else { + $name = $query->bind($value, $bindType); + $value = ':' . $name; + } + } + + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindType, isset($val[2]) ? $val[2] : 'AND'); + break; + } + } + + if (!isset($whereStr)) { + throw new Exception('where express error:' . $exp); + } + + return $whereStr; + } + + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, $key, $exp, $value, $field, $bindType, $logic) + { + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $name = $query->bind($item, PDO::PARAM_STR); + $array[] = $key . ' ' . $exp . ' :' . $name; + } + + $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, $key, $exp, array $value, $field, $bindType) + { + // 字段比较查询 + list($op, $field2) = $value; + + if (!in_array($op, ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field2, true) . ' )'; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param Expression $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, $key, $exp, Expression $value, $field, $bindType) + { + // 表达式查询 + return '( ' . $key . ' ' . $value->getValue() . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, $key, $exp, $value, $field, $bindType) + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, $key, $exp, $value, $field, $bindType) + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + $min = $query->bind($data[0], $bindType); + $max = $query->bind($data[1], $bindType); + + return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' '; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, $key, $exp, $value, $field, $bindType) + { + // EXISTS 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Expression) { + $value = $value->getValue(); + } else { + throw new Exception('where express error:' . $value); + } + + return $exp . ' (' . $value . ')'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, $key, $exp, $value, $field, $bindType) + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, $key, $exp, $value, $field, $bindType) + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value); + } + + if ('=' == $exp && is_null($value)) { + return $key . ' IS NULL'; + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, $key, $exp, $value, $field, $bindType) + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, $key, $exp, $value, $field, $bindType) + { + // IN 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Expression) { + $value = $value->getValue(); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + $array = []; + + foreach ($value as $k => $v) { + $name = $query->bind($v, $bindType); + $array[] = ':' . $name; + } + + if (count($array) == 1) { + return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0]; + } else { + $zone = implode(',', $array); + $value = empty($zone) ? "''" : $zone; + } + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, $call, $show = true) + { + $newQuery = $query->newQuery()->removeOption(); + $call($newQuery); + + return $newQuery->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @param integer $bindType + * @return string + */ + protected function parseDateTime(Query $query, $value, $key, $bindType = null) + { + $options = $query->getOptions(); + + // 获取时间字段类型 + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + + $type = $this->connection->getTableInfo($table, 'type'); + + if (isset($type[$key])) { + $info = $type[$key]; + } + + if (isset($info)) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (preg_match('/(datetime|timestamp)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + + $name = $query->bind($value, $bindType); + + return ':' . $name; + } + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, $limit) + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param Query $query 查询对象 + * @param array $join + * @return string + */ + protected function parseJoin(Query $query, $join) + { + $joinStr = ''; + + if (!empty($join)) { + foreach ($join as $item) { + list($table, $type, $on) = $item; + + $condition = []; + + foreach ((array) $on as $val) { + if ($val instanceof Expression) { + $condition[] = $val->getValue(); + } elseif (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + + $condition[] = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($query, $table); + + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } + } + + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, $order) + { + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) { + $array[] = $this->parseOrderField($query, $key, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } elseif (is_string($val)) { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * orderField分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $key + * @param array $val + * @return string + */ + protected function parseOrderField($query, $key, $val) + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + + $options = $query->getOptions(); + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + } + + /** + * group分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $group + * @return string + */ + protected function parseGroup(Query $query, $group) + { + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); + } + + /** + * having分析 + * @access protected + * @param Query $query 查询对象 + * @param string $having + * @return string + */ + protected function parseHaving(Query $query, $having) + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param Query $query 查询对象 + * @param string $comment + * @return string + */ + protected function parseComment(Query $query, $comment) + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $distinct + * @return string + */ + protected function parseDistinct(Query $query, $distinct) + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $union + * @return string + */ + protected function parseUnion(Query $query, $union) + { + if (empty($union)) { + return ''; + } + + $type = $union['type']; + unset($union['type']); + + foreach ($union as $u) { + if ($u instanceof \Closure) { + $sql[] = $type . ' ' . $this->parseClosure($query, $u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $this->connection->parseSqlTable($u) . ' )'; + } + } + + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param Query $query 查询对象 + * @param mixed $index + * @return string + */ + protected function parseForce(Query $query, $index) + { + if (empty($index)) { + return ''; + } + + return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|string $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false) + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } elseif (is_string($lock) && !empty($lock)) { + return ' ' . trim($lock) . ' '; + } + } + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function select(Query $query) + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @param bool $replace 是否replace + * @return string + */ + public function insert(Query $query, $replace = false) + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $fields = array_keys($data); + $values = array_values($data); + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace + * @return string + */ + public function insertAll(Query $query, $dataSet, $replace = false) + { + $options = $query->getOptions(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = $this->connection->getTableFields($options['table']); + } else { + $allowFields = $options['field']; + } + + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($dataSet as $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = 'SELECT ' . implode(',', array_values($data)); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + $fields = []; + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + implode(' , ', $fields), + implode(' UNION ALL ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成slect insert SQL + * @access public + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 + * @return string + */ + public function selectInsert(Query $query, $fields, $table) + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field, true); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query) + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query) + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/Connection.php b/Server/application/common/Server/thinkphp/library/think/db/Connection.php new file mode 100644 index 00000000..18b4885a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/Connection.php @@ -0,0 +1,2152 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use InvalidArgumentException; +use PDO; +use PDOStatement; +use think\Container; +use think\Db; +use think\db\exception\BindParamException; +use think\Debug; +use think\Exception; +use think\exception\PDOException; +use think\Loader; + +abstract class Connection +{ + const PARAM_FLOAT = 21; + protected static $instance = []; + /** @var PDOStatement PDO操作实例 */ + protected $PDOStatement; + + /** @var string 当前SQL指令 */ + protected $queryStr = ''; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + + /** @var PDO[] 数据库连接ID 支持多个连接 */ + protected $links = []; + + /** @var PDO 当前连接ID */ + protected $linkID; + protected $linkRead; + protected $linkWrite; + + // 查询结果类型 + protected $fetchType = PDO::FETCH_ASSOC; + // 字段属性大小写 + protected $attrCase = PDO::CASE_LOWER; + // 监听回调 + protected static $event = []; + + // 数据表信息 + protected static $info = []; + + // 使用Builder类 + protected $builderClassName; + // Builder对象 + protected $builder; + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => '', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], + ]; + + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + // 服务器断线标识字符 + protected $breakMatchStr = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + ]; + + // 绑定参数 + protected $bind = []; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 初始化 + * @access protected + * @return void + */ + protected function initialize() + {} + + /** + * 取得数据库连接类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function instance($config = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + if (empty($config['type'])) { + throw new InvalidArgumentException('Undefined db type'); + } + + // 记录初始化信息 + Container::get('app')->log('[ DB ] INIT ' . $config['type']); + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = Loader::factory($config['type'], '\\think\\db\\connector\\', $config); + } + + return self::$instance[$name]; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass() + { + if (!empty($this->builderClassName)) { + return $this->builderClassName; + } + + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + + /** + * 设置当前的数据库Builder对象 + * @access protected + * @param Builder $builder + * @return void + */ + protected function setBuilder(Builder $builder) + { + $this->builder = $builder; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn($config); + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + abstract public function getFields($tableName); + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + abstract public function getTables($dbName); + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + abstract protected function getExplain($sql); + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase($info) + { + // 字段大小写转换 + switch ($this->attrCase) { + case PDO::CASE_LOWER: + $info = array_change_key_case($info); + break; + case PDO::CASE_UPPER: + $info = array_change_key_case($info, CASE_UPPER); + break; + case PDO::CASE_NATURAL: + default: + // 不做转换 + } + + return $info; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $bind = self::PARAM_FLOAT; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + + return $bind; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) { + return $this->getConfig('prefix') . strtolower($match[1]); + }, $sql); + } + + return $sql; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($tableName) = explode(' ', $tableName); + + if (false === strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset(self::$info[$schema])) { + // 读取缓存 + $cacheFile = Container::get('app')->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $schema . '.php'; + + if (!$this->config['debug'] && is_file($cacheFile)) { + $info = include $cacheFile; + } else { + $info = $this->getFields($tableName); + } + + $fields = array_keys($info); + $bind = $type = []; + + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + + self::$info[$schema] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + + return $fetch ? self::$info[$schema][$fetch] : self::$info[$schema]; + } + + /** + * 获取数据表的主键 + * @access public + * @param string $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName) + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName) + { + return $this->getTableInfo($tableName, 'bind'); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig($config = '') + { + return $config ? $this->config[$config] : $this->config; + } + + /** + * 设置数据库的配置参数 + * @access public + * @param string|array $config 配置名称 + * @param mixed $value 配置值 + * @return void + */ + public function setConfig($config, $value = '') + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } else { + $this->config[$config] = $value; + } + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws Exception + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false) + { + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } + + if (!$config) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + + if ($config['debug']) { + $startTime = microtime(true); + } + + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + + if ($config['debug']) { + // 记录数据库连接信息 + $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + + /** + * 释放查询结果 + * @access public + */ + public function free() + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } + + return $this->linkID; + } + + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param Model $model 模型对象实例 + * @param array $condition 查询条件 + * @param mixed $relation 关联查询 + * @return \Generator + */ + public function getCursor($sql, $bind = [], $master = false, $model = null, $condition = null, $relation = null) + { + $this->initConnect($master); + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + Db::$queryTimes++; + + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false, '', $master); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + $instance = $model->newInstance($result, $condition); + + if ($relation) { + $instance->relationQuery($relation); + } + + yield $instance; + } else { + yield $result; + } + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return array + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + $this->initConnect($master); + + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + Db::$queryTimes++; + + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false, '', $master); + + // 返回结果集 + return $this->getResult($pdo, $procedure); + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + + throw $e; + } + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param Query $query 查询对象 + * @return int + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function execute($sql, $bind = [], Query $query = null) + { + $this->initConnect(true); + + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + Db::$executeTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行语句 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false, '', true); + + if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $query->readMaster(); + } + + $this->numRows = $this->PDOStatement->rowCount(); + + return $this->numRows; + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + + throw $e; + } + } + + /** + * 查找单条记录 + * @access public + * @param Query $query 查询对象 + * @return array|null|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + + $data = $options['data']; + $query->setOption('limit', 1); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + if (is_string($cache['key'])) { + $key = $cache['key']; + } else { + $key = $this->getCacheKey($query, $data); + } + + $result = Container::get('cache')->get($key); + + if (false !== $result) { + return $result; + } + } + + if (is_string($pk) && !is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + + $query->setOption('data', $data); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 事件回调 + $result = $query->trigger('before_find'); + + if (!$result) { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + + return $result; + } + + /** + * 使用游标查询记录 + * @access public + * @param Query $query 查询对象 + * @return \Generator + */ + public function cursor(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + $condition = isset($options['where']['AND']) ? $options['where']['AND'] : null; + $relation = isset($options['relaltion']) ? $options['relation'] : null; + + // 执行查询操作 + return $this->getCursor($sql, $bind, $options['master'], $query->getModel(), $condition, $relation); + } + + /** + * 查找记录 + * @access public + * @param Query $query 查询对象 + * @return array|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + $resultSet = $this->getCacheData($query, $options['cache'], null, $key); + + if (false !== $resultSet) { + return $resultSet; + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + $resultSet = $query->trigger('before_select'); + + if (!$resultSet) { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (!empty($options['cache']) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $options['cache']); + } + + return $resultSet; + } + + /** + * 插入记录 + * @access public + * @param Query $query 查询对象 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(Query $query, $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query, $replace); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); + + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $query->trigger('after_insert'); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param Query $query 查询对象 + * @param mixed $dataSet 数据集 + * @param bool $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(Query $query, $dataSet = [], $replace = false, $limit = null) + { + if (!is_array(reset($dataSet))) { + return false; + } + + $options = $query->getOptions(); + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item, $replace); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + $fetchSql[] = $this->getRealSql($sql, $bind); + } else { + $count += $this->execute($sql, $bind, $query); + } + } + + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + + return isset($fetchSql) ? implode(';', $fetchSql) : $count; + } + + $sql = $this->builder->insertAll($query, $dataSet, $replace); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + return $this->getRealSql($sql, $bind); + } + + return $this->execute($sql, $bind, $query); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param Query $query 查询对象 + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert(Query $query, $fields, $table) + { + // 分析查询表达式 + $options = $query->getOptions(); + + $table = $this->parseSqlTable($table); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + return $this->getRealSql($sql, $bind); + } + + return $this->execute($sql, $bind, $query); + } + + /** + * 更新记录 + * @access public + * @param Query $query 查询对象 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(Query $query) + { + $options = $query->getOptions(); + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + $pk = $query->getPk($options); + $data = $options['data']; + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = [$pk, '=', $data[$pk]]; + if (!isset($key)) { + $key = $this->getCacheKey($query, $data[$pk]); + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = [$field, '=', $data[$field]]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + $query->setOption('where', ['AND' => $where]); + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } + } + + // 更新数据 + $query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); + + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + + $query->setOption('data', $data); + $query->trigger('after_update'); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param Query $query 查询对象 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + $data = $options['data']; + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } elseif (!is_null($data) && true !== $data && !is_array($data)) { + $key = $this->getCacheKey($query, $data); + } elseif (is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = $this->execute($sql, $bind, $query); + + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + + $options['data'] = $data; + + $query->trigger('after_delete'); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one 是否返回一个值 + * @return mixed + */ + public function value(Query $query, $field, $default = null, $one = true) + { + $options = $query->getOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $key); + + if (false !== $result) { + return $result; + } + } + + if ($one) { + $query->setOption('limit', 1); + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + $result = $pdo->fetchColumn(); + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $aggregate 聚合方法 + * @param mixed $field 字段名 + * @return mixed + */ + public function aggregate(Query $query, $aggregate, $field) + { + if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) { + list($distinct, $field) = explode(' ', $field); + } + + $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS tp_' . strtolower($aggregate); + + return $this->value($query, $field, 0, false); + } + + /** + * 得到某个列的数组 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(Query $query, $field, $key = '') + { + $options = $query->getOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_null($field)) { + $field = ['*']; + } elseif (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + if ($key && ['*'] != $field) { + array_unshift($field, $key); + $field = array_unique($field); + } + + $query->setOption('field', $field); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $guid); + + if (false !== $result) { + return $result; + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + // 还原field参数 + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if (['*'] == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + + if (2 == $count) { + $column = $key2; + } elseif (1 == $count) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; + } + } + + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + + return $result; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function pdo(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql($sql, array $bind = []) + { + if (is_array($sql)) { + $sql = implode(';', $sql); + } + + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + + if ((self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) && is_string($value)) { + $value = '\'' . addslashes($value) . '\''; + } elseif (PDO::PARAM_INT == $type && '' === $value) { + $value = 0; + } + + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); + } + + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []) + { + foreach ($bind as $key => $val) { + // 占位符 + $param = is_int($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam($bind) + { + foreach ($bind as $key => $val) { + $param = is_int($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + $param = array_shift($val); + + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 获得数据集数组 + * @access protected + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return array + */ + protected function getResult($pdo = false, $procedure = false) + { + if ($pdo) { + // 返回PDOStatement对象处理 + return $this->PDOStatement; + } + + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + + $result = $this->PDOStatement->fetchAll($this->fetchType); + + $this->numRows = count($result); + + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure() + { + $item = []; + + do { + $result = $this->getResult(); + if ($result) { + $item[] = $result; + } + } while ($this->PDOStatement->nextRowset()); + + $this->numRows = count($item); + + return $item; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction($callback) + { + $this->startTrans(); + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + $this->commit(); + return $result; + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa($xid) + {} + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa($xid) + {} + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa($xid) + {} + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa($xid) + {} + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + ++$this->transTimes; + + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + } catch (\Exception $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->rollBack(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepointRollBack('trans' . $this->transTimes) + ); + } + + $this->transTimes = max(0, $this->transTimes - 1); + } + + /** + * 是否支持事务嵌套 + * @return bool + */ + protected function supportSavepoint() + { + return false; + } + + /** + * 生成定义保存点的SQL + * @access protected + * @param $name + * @return string + */ + protected function parseSavepoint($name) + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @access protected + * @param $name + * @return string + */ + protected function parseSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sqlArray SQL批处理指令 + * @param array $bind 参数绑定 + * @return boolean + */ + public function batchQuery($sqlArray = [], $bind = []) + { + if (!is_array($sqlArray)) { + return false; + } + + // 自动启动事务支持 + $this->startTrans(); + + try { + foreach ($sqlArray as $sql) { + $this->execute($sql, $bind); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute = false) + { + return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes() + { + return Db::$executeTimes; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + + // 释放查询 + $this->free(); + + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e) + { + if (!$this->config['break_reconnect']) { + return false; + } + + $error = $e->getMessage(); + + foreach ($this->breakMatchStr as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->linkID->lastInsertId($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->numRows; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + + return $error; + } + + /** + * 数据库调试 记录当前SQL及分析性能 + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 + * @return void + */ + protected function debug($start, $sql = '', $master = false) + { + if (!empty($this->config['debug'])) { + // 开启数据库调试模式 + $debug = Container::get('debug'); + + if ($start) { + $debug->remark('queryStartTime', 'time'); + } else { + // 记录操作结束时间 + $debug->remark('queryEndTime', 'time'); + $runtime = $debug->getRangeTime('queryStartTime', 'queryEndTime'); + $sql = $sql ?: $this->getLastsql(); + $result = []; + + // SQL性能分析 + if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { + $result = $this->getExplain($sql); + } + + // SQL监听 + $this->triggerSql($sql, $runtime, $result, $master); + } + } + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + self::$event[] = $callback; + } + + /** + * 触发SQL事件 + * @access protected + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 + * @param bool $master 主从标记 + * @return void + */ + protected function triggerSql($sql, $runtime, $explain = [], $master = false) + { + if (!empty(self::$event)) { + foreach (self::$event as $callback) { + if (is_callable($callback)) { + call_user_func_array($callback, [$sql, $runtime, $explain, $master]); + } + } + } else { + if ($this->config['deploy']) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + // 未注册监听则记录到日志中 + $this->log('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]'); + + if (!empty($explain)) { + $this->log('[ EXPLAIN : ' . var_export($explain, true) . ' ]'); + } + } + } + + public function log($log, $type = 'sql') + { + $this->config['debug'] && Container::get('log')->record($log, $type); + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect($master = true) + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master || $this->transTimes) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->linkID = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->linkID = $this->linkRead; + } + } elseif (!$this->linkID) { + // 默认单数据库 + $this->linkID = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return PDO + */ + protected function multiConnect($master = false) + { + $_config = []; + + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $_config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + $r = $m; + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($_config['hostname']) - 1)); + } + $dbMaster = false; + + if ($m != $r) { + $dbMaster = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; + } + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; + } + + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 关闭连接 + $this->close(); + } + + /** + * 缓存数据 + * @access protected + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + $cache = Container::get('cache'); + + if (isset($config['tag'])) { + $cache->tag($config['tag'])->set($key, $data, $config['expire']); + } else { + $cache->set($key, $data, $config['expire']); + } + } + + /** + * 获取缓存数据 + * @access protected + * @param Query $query 查询对象 + * @param mixed $cache 缓存设置 + * @param array $options 缓存 + * @return mixed + */ + protected function getCacheData(Query $query, $cache, $data, &$key = null) + { + // 判断查询缓存 + $key = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $data); + + return Container::get('cache')->get($key); + } + + /** + * 生成缓存标识 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 缓存数据 + * @return string + */ + protected function getCacheKey(Query $query, $value) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'], true) && is_scalar($value[2])) { + $data = $value[2]; + } + + $prefix = 'think:' . $this->getConfig('database') . '.'; + + if (isset($data)) { + return $prefix . $query->getTable() . '|' . $data; + } + + try { + return md5($prefix . serialize($query->getOptions()) . serialize($query->getBind(false))); + } catch (\Exception $e) { + throw new Exception('closure not support cache(true)'); + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/Expression.php b/Server/application/common/Server/thinkphp/library/think/db/Expression.php new file mode 100644 index 00000000..f1b92abd --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/Expression.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +class Expression +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/Query.php b/Server/application/common/Server/thinkphp/library/think/db/Query.php new file mode 100644 index 00000000..ba082794 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/Query.php @@ -0,0 +1,3766 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Collection; +use think\Container; +use think\Db; +use think\db\exception\BindParamException; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Exception; +use think\exception\DbException; +use think\exception\PDOException; +use think\Loader; +use think\Model; +use think\model\Collection as ModelCollection; +use think\model\Relation; +use think\model\relation\OneToOne; +use think\Paginator; + +class Query +{ + /** + * 当前数据库连接对象 + * @var Connection + */ + protected $connection; + + /** + * 当前模型对象 + * @var Model + */ + protected $model; + + /** + * 当前数据表名称(不含前缀) + * @var string + */ + protected $name = ''; + + /** + * 当前数据表主键 + * @var string|array + */ + protected $pk; + + /** + * 当前数据表前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 当前查询参数 + * @var array + */ + protected $options = []; + + /** + * 当前参数绑定 + * @var array + */ + protected $bind = []; + + /** + * 事件回调 + * @var array + */ + private static $event = []; + + /** + * 扩展查询方法 + * @var array + */ + private static $extend = []; + + /** + * 读取主库的表 + * @var array + */ + protected static $readMaster = []; + + /** + * 日期查询表达式 + * @var array + */ + protected $timeRule = [ + 'today' => ['today', 'tomorrow -1second'], + 'yesterday' => ['yesterday', 'today -1second'], + 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'], + 'year' => ['this year 1/1', 'next year 1/1 -1second'], + 'last year' => ['last year 1/1', 'this year 1/1 -1second'], + ]; + + /** + * 日期查询快捷定义 + * @var array + */ + protected $timeExp = ['d' => 'today', 'w' => 'week', 'm' => 'month', 'y' => 'year']; + + /** + * 架构函数 + * @access public + */ + public function __construct(Connection $connection = null) + { + if (is_null($connection)) { + $this->connection = Db::connect(); + } else { + $this->connection = $connection; + } + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 创建一个新的查询对象 + * @access public + * @return Query + */ + public function newQuery() + { + $query = new static($this->connection); + + if ($this->model) { + $query->model($this->model); + } + + if (isset($this->options['table'])) { + $query->table($this->options['table']); + } else { + $query->name($this->name); + } + + if (isset($this->options['json'])) { + $query->json($this->options['json'], $this->options['json_assoc']); + } + + if (isset($this->options['field_type'])) { + $query->setJsonFieldType($this->options['field_type']); + } + + return $query; + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws DbException + * @throws Exception + */ + public function __call($method, $args) + { + if (isset(self::$extend[strtolower($method)])) { + // 调用扩展查询方法 + array_unshift($args, $this); + + return Container::getInstance() + ->invoke(self::$extend[strtolower($method)], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Loader::parseName(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Loader::parseName(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Loader::parseName(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Loader::parseName(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; + } else { + throw new Exception('method not exist:' . ($this->model ? get_class($this->model) : static::class) . '->' . $method); + } + } + + /** + * 扩展查询方法 + * @access public + * @param string|array $method 查询方法名 + * @param callable $callback + * @return void + */ + public static function extend($method, $callback = null) + { + if (is_array($method)) { + foreach ($method as $key => $val) { + self::$extend[strtolower($key)] = $val; + } + } else { + self::$extend[strtolower($method)] = $callback; + } + } + + /** + * 设置当前的数据库Connection对象 + * @access public + * @param Connection $connection + * @return $this + */ + public function setConnection(Connection $connection) + { + $this->connection = $connection; + $this->prefix = $this->connection->getConfig('prefix'); + + return $this; + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 指定模型 + * @access public + * @param Model $model 模型对象实例 + * @return $this + */ + public function model(Model $model) + { + $this->model = $model; + return $this; + } + + /** + * 获取当前的模型对象 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model ? $this->model->setQuery($this) : null; + } + + /** + * 设置从主库读取数据 + * @access public + * @param bool $all 是否所有表有效 + * @return $this + */ + public function readMaster($all = false) + { + $table = $all ? '*' : $this->getTable(); + + static::$readMaster[$table] = true; + + return $this; + } + + /** + * 指定当前数据表名(不含前缀) + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + return $this; + } + + /** + * 获取当前的数据表名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name ?: $this->model->getName(); + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name + * @return string + */ + public function getTable($name = '') + { + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; + } + + $name = $name ?: $this->name; + + return $this->prefix . Loader::parseName($name); + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return mixed + * @throws BindParamException + * @throws PDOException + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + return $this->connection->query($sql, $bind, $master, $pdo); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws PDOException + */ + public function execute($sql, $bind = []) + { + return $this->connection->execute($sql, $bind, $this); + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + $this->connection->listen($callback); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->connection->getLastInsID($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->connection->getNumRows(); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->connection->getLastSql(); + } + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa($callback, array $dbs = []) + { + $xid = uniqid('xa'); + + if (empty($dbs)) { + $dbs[] = $this->getConnection(); + } + + foreach ($dbs as $key => $db) { + if ($db instanceof Query) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransXa($xid); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + foreach ($dbs as $db) { + $db->prepareXa($xid); + } + + foreach ($dbs as $db) { + $db->commitXa($xid); + } + + return $result; + } catch (\Exception $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } catch (\Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction($callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->connection->rollback(); + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return boolean + */ + public function batchQuery($sql = []) + { + return $this->connection->batchQuery($sql); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return mixed + */ + public function getConfig($name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = '') + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName = '', $field = null) + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getFieldsType($tableName, $field); + } + + /** + * 得到分表的的数据表名 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return array + */ + public function getPartitionTableName($data, $field, $rule = []) + { + // 对数据表进行分区 + if ($field && isset($data[$field])) { + $value = $data[$field]; + $type = $rule['type']; + switch ($type) { + case 'id': + // 按照id范围分表 + $step = $rule['expr']; + $seq = floor($value / $step) + 1; + break; + case 'year': + // 按照年份分表 + if (!is_numeric($value)) { + $value = strtotime($value); + } + $seq = date('Y', $value) - $rule['expr'] + 1; + break; + case 'mod': + // 按照id的模数分表 + $seq = ($value % $rule['num']) + 1; + break; + case 'md5': + // 按照md5的序列分表 + $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1; + break; + default: + if (function_exists($type)) { + // 支持指定函数哈希 + $value = $type($value); + } + + $seq = (ord(substr($value, 0, 1)) % $rule['num']) + 1; + } + + return $this->getTable() . '_' . $seq; + } + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + return ['( ' . implode(" UNION ", $tableName) . ' )' => $this->name]; + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value($field, $default = null) + { + $this->parseOptions(); + + return $this->connection->value($this, $field, $default); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, $key = '') + { + $this->parseOptions(); + + return $this->connection->column($this, $field, $key); + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + $this->parseOptions(); + + $result = $this->connection->aggregate($this, $aggregate, $field); + + if (!empty($this->options['fetch_sql'])) { + return $result; + } elseif ($force) { + $result = (float) $result; + } + + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string|Expression $field 字段名 + * @return float|string + */ + public function count($field = '*') + { + if (!empty($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options) + ->field('count(' . $field . ') AS think_count') + ->bind($this->bind) + ->buildSql(); + + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + if (!empty($options['fetch_sql'])) { + $query->fetchSql(true); + } + + $count = $query->aggregate('COUNT', '*', true); + } else { + $count = $this->aggregate('COUNT', $field, true); + } + + return is_string($count) ? $count : (int) $count; + } + + /** + * SUM查询 + * @access public + * @param string|Expression $field 字段名 + * @return float + */ + public function sum($field) + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string|Expression $field 字段名 + * @return float + */ + public function avg($field) + { + return $this->aggregate('AVG', $field, true); + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param mixed $value 字段值 + * @return integer + */ + public function setField($field, $value = '') + { + if (is_array($field)) { + $data = $field; + } else { + $data[$field] = $value; + } + + return $this->update($data); + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); + $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + } + + return $this->setField($field, ['INC', $step]); + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); + $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + + $value = ['INC', $step]; + } else { + $value = ['DEC', $step]; + } + + return $this->setField($field, $value); + } + + /** + * 延时更新检查 返回false表示需要延时 + * 否则返回实际写入的数值 + * @access protected + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) + * @return false|integer + */ + protected function lazyWrite($type, $guid, $step, $lazyTime) + { + $cache = Container::get('cache'); + + if (!$cache->has($guid . '_time')) { + // 计时开始 + $cache->set($guid . '_time', time(), 0); + $cache->$type($guid, $step); + } elseif (time() > $cache->get($guid . '_time') + $lazyTime) { + // 删除缓存 + $value = $cache->$type($guid, $step); + $cache->rm($guid); + $cache->rm($guid . '_time'); + return 0 === $value ? false : $value; + } else { + // 更新缓存 + $cache->$type($guid, $step); + } + + return false; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function join($join, $condition = null, $type = 'INNER', $bind = []) + { + if (empty($condition)) { + // 如果为组数,则循环调用join + foreach ($join as $key => $value) { + if (is_array($value) && 2 <= count($value)) { + $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type); + } + } + } else { + $table = $this->getJoinTable($join); + if ($bind) { + $this->bindParams($condition, $bind); + } + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + + return $this; + } + + /** + * LEFT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function leftJoin($join, $condition = null, $bind = []) + { + return $this->join($join, $condition, 'LEFT'); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function rightJoin($join, $condition = null, $bind = []) + { + return $this->join($join, $condition, 'RIGHT'); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function fullJoin($join, $condition = null, $bind = []) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string $join + * @param string $alias + * @return string + */ + protected function getJoinTable($join, &$alias = null) + { + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + } else { + $join = trim($join); + + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); + } + } + + if (isset($alias) && $table != $alias) { + $table = [$table => $alias]; + } + } + + return $table; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return $this + */ + public function union($union, $all = false) + { + if (empty($union)) { + return $this; + } + + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; + + if (is_array($union)) { + $this->options['union'] = array_merge($this->options['union'], $union); + } else { + $this->options['union'][] = $union; + } + + return $this; + } + + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + + /** + * 指定查询字段 支持字段排除和指定数据表 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['field'][] = $field; + return $this; + } + + if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields($tableName); + $field = $fields ?: ['*']; + } elseif ($except) { + // 字段排除 + $fields = $this->getTableFields($tableName); + $field = $fields ? array_diff($fields, $field) : $field; + } + + if ($tableName) { + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => &$val) { + if (is_numeric($key) && $alias) { + $field[$prefix . '.' . $val] = $alias . $val; + unset($field[$key]); + } elseif (is_numeric($key)) { + $val = $prefix . '.' . $val; + } + } + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @return $this + */ + public function fieldRaw($field) + { + $this->options['field'][] = $this->raw($field); + + return $this; + } + + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1, $op = 'INC') + { + $fields = is_string($field) ? explode(',', $field) : $field; + + foreach ($fields as $field => $val) { + if (is_numeric($field)) { + $field = $val; + } else { + $step = $val; + } + + $this->data($field, [$op, $step]); + } + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + return $this->inc($field, $step, 'DEC'); + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, $this->raw($value)); + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param mixed $value 表达式 + * @return Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param mixed $on JOIN条件 + * @param string $type JOIN类型 + * @return $this + */ + public function view($join, $field = true, $on = null, $type = 'INNER') + { + $this->options['view'] = true; + + if (is_array($join) && key($join) === 0) { + foreach ($join as $key => $val) { + $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); + } + } else { + $fields = []; + $table = $this->getJoinTable($join, $alias); + + if (true === $field) { + $fields = $alias . '.*'; + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $fields[] = $alias . '.' . $val; + + $this->options['map'][$val] = $alias . '.' . $val; + } else { + if (preg_match('/[,=\.\'\"\(\s]/', $key)) { + $name = $key; + } else { + $name = $alias . '.' . $key; + } + + $fields[] = $name . ' AS ' . $val; + + $this->options['map'][$val] = $name; + } + } + } + + $this->field($fields); + + if ($on) { + $this->join($table, $on, $type); + } else { + $this->table($table); + } + } + + return $this; + } + + /** + * 设置分表规则 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return $this + */ + public function partition($data, $field, $rule = []) + { + $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + + return $this; + } + + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('AND', $field, $op, $condition, $param); + } + + /** + * 指定OR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereOr($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('OR', $field, $op, $condition, $param); + } + + /** + * 指定XOR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereXor($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); + } + + /** + * 比较两个字段 + * @access public + * @param string|array $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereColumn($field1, $operator = null, $field2 = null, $logic = 'AND') + { + if (is_array($field1)) { + foreach ($field1 as $item) { + $this->whereColumn($item[0], $item[1], isset($item[2]) ? $item[2] : null); + } + return $this; + } + + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); + } + + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition]; + } + + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $where, $bind = [], $logic = 'AND') + { + if ($bind) { + $this->bindParams($where, $bind); + } + + $this->options['where'][$logic][] = [$field, 'EXP', $this->raw($where)]; + + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw($where, $bind = [], $logic = 'AND') + { + if ($bind) { + $this->bindParams($where, $bind); + } + + $this->options['where'][$logic][] = $this->raw($where); + + return $this; + } + + /** + * 参数绑定 + * @access public + * @param string $sql 绑定的sql表达式 + * @param array $bind 参数绑定 + * @return void + */ + protected function bindParams(&$sql, array $bind = []) + { + foreach ($bind as $key => $value) { + if (is_array($value)) { + $name = $this->bind($value[0], $value[1], isset($value[2]) ? $value[2] : null); + } else { + $name = $this->bind($value); + } + + if (is_numeric($key)) { + $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, ':' . $name, $sql); + } + } + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw($where, $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return $this + */ + protected function parseWhereExp($logic, $field, $op, $condition, array $param = [], $strict = false) + { + if ($field instanceof $this) { + $this->options['where'] = $field->getOptions('where'); + $this->bind($field->getBind(false)); + return $this; + } + + $logic = strtoupper($logic); + + if ($field instanceof Where) { + $this->options['where'][$logic] = $field->parse(); + return $this; + } + + if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Expression) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif ($strict) { + // 使用严格模式查询 + $where = [$field, $op, $condition, $logic]; + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof \Closure) { + $where = $field; + } elseif (is_string($field)) { + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, $op, $logic); + } elseif (is_string($op) && strtolower($op) == 'exp') { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; + return $this->whereExp($field, $condition, $bind, $logic); + } + + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + $this->options['where'][$logic][] = $where; + } + + return $this; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return mixed + */ + protected function parseWhereItem($logic, $field, $op, $condition, $param = []) + { + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } elseif ($field && is_null($condition)) { + if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) { + $where = [$field, 'NULL', '']; + } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition]; + } else { + $where = $field ? [$field, $op, $condition, isset($param[2]) ? $param[2] : null] : null; + } + + return $where; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems($field, $logic) + { + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if ($val instanceof Expression) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } else { + $where[] = [$key, is_array($val) ? 'IN' : '=', $val]; + } + } + } else { + // 数组批量查询 + $where = $field; + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } + } + + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + $this->bind = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param \Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param \Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof \Closure) { + $condition = $condition($this); + } + + if ($condition) { + if ($query instanceof \Closure) { + $query($this, $condition); + } elseif (is_array($query)) { + $this->where($query); + } + } elseif ($otherwise) { + if ($otherwise instanceof \Closure) { + $otherwise($this, $condition); + } elseif (is_array($otherwise)) { + $this->where($otherwise); + } + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return $this + */ + public function limit($offset, $length = null) + { + if (is_null($length) && strpos($offset, ',')) { + list($offset, $length) = explode(',', $offset); + } + + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return $this + */ + public function page($page, $listRows = null) + { + if (is_null($listRows) && strpos($page, ',')) { + list($page, $listRows) = explode(',', $page); + } + + $this->options['page'] = [intval($page), intval($listRows)]; + + return $this; + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return $this[]|\think\Paginator + * @throws DbException + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $paginate = Container::get('config')->pull('paginate'); + + if (is_array($listRows)) { + $config = array_merge($paginate, $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge($paginate, $config); + $listRows = $listRows ?: $config['list_rows']; + } + + /** @var Paginator $class */ + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); + $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ + $class, + 'getCurrentPage', + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $bind = $this->bind; + $total = $this->count(); + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return $class::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $tables = $table; + $table = []; + + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } + + $this->options['table'] = $table; + + return $this; + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, $order = null) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['order'][] = $field; + return $this; + } + + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; + } + + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } + } + } + + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } + + return $this; + } + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw($field, $bind = []) + { + if ($bind) { + $this->bindParams($field, $bind); + } + + $this->options['order'][] = $this->raw($field); + + return $this; + } + + /** + * 指定Field排序 order('id',[1,2,3],'desc') + * @access public + * @param string|array $field 排序字段 + * @param array $values 排序值 + * @param string $order + * @return $this + */ + public function orderField($field, array $values, $order = '') + { + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + } + + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) + if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (false !== $key) { + $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; + } + + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string|array $group GROUP + * @return $this + */ + public function group($group) + { + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return $this + */ + public function having($having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + $this->options['master'] = true; + + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param string $distinct 是否唯一 + * @return $this + */ + public function distinct($distinct) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param array|string $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + foreach ($alias as $key => $val) { + if (false !== strpos($key, '__')) { + $table = $this->connection->parseSqlTable($key); + } else { + $table = $key; + } + $this->options['alias'][$table] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->connection->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } + + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force($force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 获取执行的SQL语句 + * @access public + * @param boolean $fetch 是否返回sql + * @return $this + */ + public function fetchSql($fetch = true) + { + $this->options['fetch_sql'] = $fetch; + return $this; + } + + /** + * 不主动获取数据集 + * @access public + * @param bool $pdo 是否返回 PDOStatement 对象 + * @return $this + */ + public function fetchPdo($pdo = true) + { + $this->options['fetch_pdo'] = $pdo; + return $this; + } + + /** + * 设置是否返回数据集对象(支持设置数据集对象类名) + * @access public + * @param bool|string $collection 是否返回数据集对象 + * @return $this + */ + public function fetchCollection($collection = true) + { + $this->options['collection'] = $collection; + + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @return $this + */ + public function master() + { + $this->options['master'] = true; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict($strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException($fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence($sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param mixed $hidden 需要隐藏的字段名 + * @return $this + */ + public function hidden($hidden) + { + if ($this->model) { + $this->options['hidden'] = $hidden; + return $this; + } + + return $this->field($hidden, true); + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible 需要输出的属性 + * @return $this + */ + public function visible(array $visible) + { + $this->options['visible'] = $visible; + return $this; + } + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append = []) + { + $this->options['append'] = $append; + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + if (is_array($name)) { + $this->options['with_attr'] = $name; + } else { + $this->options['with_attr'][$name] = $callback; + } + + return $this; + } + + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 + * @return $this + */ + public function json(array $json = [], $assoc = false) + { + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; + return $this; + } + + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setJsonFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getJsonFieldType($field) + { + return isset($this->options['field_type'][$field]) ? $this->options['field_type'][$field] : null; + } + + /** + * 是否允许返回空数据(或空模型) + * @access public + * @param bool $allowEmpty 是否允许为空 + * @return $this + */ + public function allowEmpty($allowEmpty = true) + { + $this->options['allow_empty'] = $allowEmpty; + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|\Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof \Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + + /** + * 使用搜索器条件搜索字段 + * @access public + * @param array $fields 搜索字段 + * @param array $data 搜索数据 + * @param string $prefix 字段前缀标识 + * @return $this + */ + public function withSearch(array $fields, array $data = [], $prefix = '') + { + foreach ($fields as $key => $field) { + if ($field instanceof \Closure) { + $field($this, isset($data[$key]) ? $data[$key] : null, $data, $prefix); + } elseif ($this->model) { + // 检测搜索器 + $fieldName = is_numeric($key) ? $field : $key; + $method = 'search' . Loader::parseName($fieldName, 1) . 'Attr'; + + if (method_exists($this->model, $method)) { + $this->model->$method($this, isset($data[$field]) ? $data[$field] : null, $data, $prefix); + } + } + } + + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $name 时间表达式 + * @param string|array $rule 时间范围 + * @return $this + */ + public function timeRule($name, $rule) + { + $this->timeRule[$name] = $rule; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @param string $logic AND OR + * @return $this + */ + public function whereTime($field, $op, $range = null, $logic = 'AND') + { + if (is_null($range)) { + if (is_array($op)) { + $range = $op; + } else { + if (isset($this->timeExp[strtolower($op)])) { + $op = $this->timeExp[strtolower($op)]; + } + + if (isset($this->timeRule[strtolower($op)])) { + $range = $this->timeRule[strtolower($op)]; + } else { + $range = $op; + } + } + + $op = is_array($range) ? 'between' : '>='; + } + + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); + } + + /** + * 查询当前时间在两个时间字段范围 + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereBetweenTimeField($startField, $endField) + { + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField($startField, $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + + /** + * 查询日期或者时间范围 + * @access public + * @param string $field 日期字段名 + * @param string $startTime 开始时间 + * @param string $endTime 结束时间 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTime($field, $startTime, $endTime = null, $logic = 'AND') + { + if (is_null($endTime)) { + $time = is_string($startTime) ? strtotime($startTime) : $startTime; + $endTime = strtotime('+1 day', $time); + } + + return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true); + } + + /** + * 获取当前数据表的主键 + * @access public + * @param string|array $options 数据表名或者查询参数 + * @return string|array + */ + public function getPk($options = '') + { + if (!empty($this->pk)) { + $pk = $this->pk; + } else { + $pk = $this->connection->getPk(is_array($options) && isset($options['table']) ? $options['table'] : $this->getTable()); + } + + return $pk; + } + + /** + * 参数绑定 + * @access public + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @param string $name 绑定名称 + * @return $this|string + */ + public function bind($value, $type = PDO::PARAM_STR, $name = null) + { + if (is_array($value)) { + $this->bind = array_merge($this->bind, $value); + } else { + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_'; + + $this->bind[$name] = [$value, $type]; + return $name; + } + + return $this; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 查询参数赋值 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value) + { + $this->options[$name] = $value; + return $this; + } + + /** + * 查询参数赋值 + * @access protected + * @param array $options 表达式参数 + * @return $this + */ + protected function options(array $options) + { + $this->options = $options; + return $this; + } + + /** + * 获取当前的查询参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOptions($name = '') + { + if ('' === $name) { + return $this->options; + } + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption($option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * 设置关联查询JOIN预查询 + * @access public + * @param string|array $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $closure = null; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $table = $model->getTable(); + $model->removeOption() + ->table($table) + ->eagerly($this, $relation, true, '', $closure, $first); + $first = false; + } + } + + $this->via(); + + $this->options['with'] = $with; + + return $this; + } + + /** + * 关联预载入 JOIN方式(不支持嵌套) + * @access protected + * @param string|array $with 关联方法名 + * @param string $joinType JOIN方式 + * @return $this + */ + public function withJoin($with, $joinType = '') + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $closure = null; + $field = true; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $field = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + + if ($model instanceof OneToOne) { + $model->eagerly($this, $relation, $field, $joinType, $closure, $first); + $first = false; + } else { + // 不支持其它关联 + unset($with[$key]); + } + } + + $this->via(); + + $this->options['with_join'] = $with; + + return $this; + } + + /** + * 关联统计 + * @access protected + * @param string|array $relation 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + protected function withAggregate($relation, $aggregate = 'count', $field = '*', $subQuery = true) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + if (!$subQuery) { + $this->options['with_count'][] = [$relations, $aggregate, $field]; + } else { + if (!isset($this->options['field'])) { + $this->field('*'); + } + + foreach ($relations as $key => $relation) { + $closure = $aggregateField = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (!is_int($key)) { + $aggregateField = $relation; + $relation = $key; + } + + $relation = Loader::parseName($relation, 1, false); + + $count = $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField); + + if (empty($aggregateField)) { + $aggregateField = Loader::parseName($relation) . '_' . $aggregate; + } + + $this->field(['(' . $count . ')' => $aggregateField]); + } + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + return $this->withAggregate($relation, 'count', '*', $subQuery); + } + + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } + + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } + + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); + } + + /** + * 关联预加载中 获取关联指定字段值 + * example: + * Model::with(['relation' => function($query){ + * $query->withField("id,name"); + * }]) + * + * @access public + * @param string | array $field 指定获取的字段 + * @return $this + */ + public function withField($field) + { + $this->options['with_field'] = $field; + + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via + * @return $this + */ + public function via($via = '') + { + $this->options['via'] = $via; + + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param string|array $relation 关联名称 + * @return $this + */ + public function relation($relation) + { + if (empty($relation)) { + return $this; + } + + if (is_string($relation)) { + $relation = explode(',', $relation); + } + + if (isset($this->options['relation'])) { + $this->options['relation'] = array_merge($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } + + return $this; + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) + { + $this->parseOptions(); + + $this->options['data'] = array_merge($this->options['data'], $data); + + return $this->connection->insert($this, $replace, $getLastInsID, $sequence); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insertGetId(array $data, $replace = false, $sequence = null) + { + return $this->insert($data, $replace, true, $sequence); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + */ + public function insertAll(array $dataSet = [], $replace = false, $limit = null) + { + $this->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $this->options['data']; + } + + if (empty($limit) && !empty($this->options['limit'])) { + $limit = $this->options['limit']; + } + + return $this->connection->insertAll($this, $dataSet, $replace, $limit); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert($fields, $table) + { + $this->parseOptions(); + + return $this->connection->selectInsert($this, $fields, $table); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(array $data = []) + { + $this->parseOptions(); + + $this->options['data'] = array_merge($this->options['data'], $data); + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + $this->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + if ($condition) { + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); + } + } + + $this->options['data'] = $data; + + return $this->connection->delete($this); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + $this->parseOptions(); + + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return \Generator + */ + public function cursor($data = null) + { + if ($data instanceof \Closure) { + $data($this); + $data = null; + } + + $this->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); + } + + /** + * 查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection|array|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null) + { + if ($data instanceof Query) { + return $data->select(); + } elseif ($data instanceof \Closure) { + $data($this); + $data = null; + } + + $this->parseOptions(); + + if (false === $data) { + // 用于子查询 不查询只返回SQL + $this->options['fetch_sql'] = true; + } elseif (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $resultSet = $this->connection->select($this); + + if ($this->options['fetch_sql']) { + return $resultSet; + } + + // 返回结果处理 + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($this->options); + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $resultSet = $this->resultSetToModelCollection($resultSet); + } else { + $this->resultSet($resultSet); + } + + return $resultSet; + } + + /** + * 查询数据转换为模型数据集对象 + * @access protected + * @param array $resultSet 数据集 + * @return ModelCollection + */ + protected function resultSetToModelCollection(array $resultSet) + { + if (!empty($this->options['collection']) && is_string($this->options['collection'])) { + $collection = $this->options['collection']; + } + + if (empty($resultSet)) { + return $this->model->toCollection([], isset($collection) ? $collection : null); + } + + // 检查动态获取器 + if (!empty($this->options['with_attr'])) { + foreach ($this->options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + list($relation, $field) = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($this->options['with_attr'][$name]); + } + } + } + + $withRelationAttr = isset($withRelationAttr) ? $withRelationAttr : []; + + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result, $this->options, true, $withRelationAttr); + } + + if (!empty($this->options['with'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr); + } + + if (!empty($this->options['with_join'])) { + // JOIN预载入 + $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true); + } + + // 模型数据集转换 + return $result->toCollection($resultSet, isset($collection) ? $collection : null); + } + + /** + * 处理数据集 + * @access public + * @param array $resultSet + * @return void + */ + protected function resultSet(&$resultSet) + { + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } + } + + if (!empty($this->options['with_attr'])) { + foreach ($resultSet as &$result) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + if (!empty($this->options['collection']) || 'collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + } + + /** + * 查找单条记录 + * @access public + * @param array|string|Query|\Closure $data + * @return array|null|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if ($data instanceof Query) { + return $data->find(); + } elseif ($data instanceof \Closure) { + $data($this); + $data = null; + } + + $this->parseOptions(); + + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $result = $this->connection->find($this); + + if ($this->options['fetch_sql']) { + return $result; + } + + // 数据处理 + if (empty($result)) { + return $this->resultToEmpty(); + } + + if (!empty($this->model)) { + // 返回模型对象 + $this->resultToModel($result, $this->options); + } else { + $this->result($result); + } + + return $result; + } + + /** + * 处理空数据 + * @access protected + * @return array|Model|null + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function resultToEmpty() + { + if (!empty($this->options['allow_empty'])) { + return !empty($this->model) ? $this->model->newInstance([], $this->getModelUpdateCondition($this->options)) : []; + } elseif (!empty($this->options['fail'])) { + $this->throwNotFound($this->options); + } + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @param bool $failException 是否抛出异常 + * @return static|null + * @throws exception\DbException + */ + public function get($data, $with = [], $cache = false, $failException = false) + { + if (is_null($data)) { + return; + } + + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + + return $this->parseQuery($data, $with, $cache) + ->failException($failException) + ->find($data); + } + + /** + * 查找单条记录 如果不存在直接抛出异常 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public function getOrFail($data, $with = [], $cache = false) + { + return $this->get($data, $with, $cache, true); + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static[]|false + * @throws exception\DbException + */ + public function all($data = null, $with = [], $cache = false) + { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + + return $this->parseQuery($data, $with, $cache)->select($data); + } + + /** + * 分析查询表达式 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return Query + */ + protected function parseQuery(&$data, $with, $cache) + { + $result = $this->with($with)->cache($cache); + + if ((is_array($data) && key($data) !== 0) || $data instanceof Where) { + $result = $result->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($result); + $data = null; + } elseif ($data instanceof Query) { + $result = $data->with($with)->cache($cache); + $data = null; + } + + return $result; + } + + /** + * 处理数据 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function result(&$result) + { + if (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); + } + + if (!empty($this->options['with_attr'])) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + /** + * 使用获取器处理数据 + * @access protected + * @param array $result 查询数据 + * @param array $withAttr 字段获取器 + * @return void + */ + protected function getResultAttr(&$result, $withAttr = []) + { + foreach ($withAttr as $name => $closure) { + $name = Loader::parseName($name); + + if (strpos($name, '.')) { + // 支持JSON字段 获取器定义 + list($key, $field) = explode('.', $name); + + if (isset($result[$key])) { + $result[$key][$field] = $closure(isset($result[$key][$field]) ? $result[$key][$field] : null, $result[$key]); + } + } else { + $result[$name] = $closure(isset($result[$name]) ? $result[$name] : null, $result); + } + } + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + protected function jsonResult(&$result, $json = [], $assoc = false, $withRelationAttr = []) + { + foreach ($json as $name) { + if (isset($result[$name])) { + $result[$name] = json_decode($result[$name], $assoc); + + if (isset($withRelationAttr[$name])) { + foreach ($withRelationAttr[$name] as $key => $closure) { + $data = get_object_vars($result[$name]); + $result[$name]->$key = $closure(isset($result[$name]->$key) ? $result[$name]->$key : null, $data); + } + } + } + } + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @param array $options 查询参数 + * @param bool $resultSet 是否为数据集查询 + * @param array $withRelationAttr 关联字段获取器 + * @return void + */ + protected function resultToModel(&$result, $options = [], $resultSet = false, $withRelationAttr = []) + { + // 动态获取器 + if (!empty($options['with_attr']) && empty($withRelationAttr)) { + foreach ($options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + list($relation, $field) = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($options['with_attr'][$name]); + } + } + } + + // JSON 数据处理 + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr); + } + + $result = $this->model->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options)); + + // 动态获取器 + if (!empty($options['with_attr'])) { + $result->withAttribute($options['with_attr']); + } + + // 输出属性控制 + if (!empty($options['visible'])) { + $result->visible($options['visible'], true); + } elseif (!empty($options['hidden'])) { + $result->hidden($options['hidden'], true); + } + + if (!empty($options['append'])) { + $result->append($options['append'], true); + } + + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation'], $withRelationAttr); + } + + // 预载入查询 + if (!$resultSet && !empty($options['with'])) { + $result->eagerlyResult($result, $options['with'], $withRelationAttr); + } + + // JOIN预载入查询 + if (!$resultSet && !empty($options['with_join'])) { + $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true); + } + + // 关联统计 + if (!empty($options['with_count'])) { + foreach ($options['with_count'] as $val) { + $result->relationCount($result, $val[0], $val[1], $val[2]); + } + } + } + + /** + * 获取模型的更新条件 + * @access protected + * @param array $options 查询参数 + */ + protected function getModelUpdateCondition(array $options) + { + return isset($options['where']['AND']) ? $options['where']['AND'] : null; + } + + /** + * 查询失败 抛出异常 + * @access protected + * @param array $options 查询参数 + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound($options = []) + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); + } + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 查找单条记录 不存在则返回空模型 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrEmpty($data = null) + { + return $this->allowEmpty(true)->find($data); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return boolean + * @throws DbException + */ + public function chunk($count, $callback, $column = null, $order = 'asc') + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk($options); + + if (isset($options['order'])) { + if (Container::get('app')->isDebug()) { + throw new DbException('chunk not support call order'); + } + unset($options['order']); + } + + $bind = $this->bind; + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } + + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = end($resultSet); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->bind($bind)->order($column, $order)->select(); + } + + return true; + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @param bool $clear + * @return array + */ + public function getBind($clear = true) + { + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + + return $bind; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub + * @return string + * @throws DbException + */ + public function buildSql($sub = true) + { + return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false); + } + + /** + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void + */ + protected function parseView(&$options) + { + if (!isset($options['map'])) { + return; + } + + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key) && is_string($val)) { + if (strpos($val, ' ')) { + list($field, $sort) = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } + } + } + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data) + { + $pk = $this->getPk($this->options); + // 获取当前数据表 + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; + } else { + $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$key] = [$attr, '=', $data[$key]]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($this->options['where']['AND'])) { + $this->options['where']['AND'] = array_merge($this->options['where']['AND'], $where); + } else { + $this->options['where']['AND'] = $where; + } + } + + return; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access protected + * @return array + */ + protected function parseOptions() + { + $options = $this->getOptions(); + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + $this->parseView($options); + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + foreach (['data', 'order', 'join', 'union'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { + $options['master'] = true; + } + + foreach (['group', 'having', 'limit', 'force', 'comment'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + list($page, $listRows) = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = $options; + + return $options; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @return bool + */ + public function trigger($event) + { + $result = false; + + if (isset(self::$event[$event])) { + $result = Container::getInstance()->invoke(self::$event[$event], [$this]); + } + + return $result; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/Where.php b/Server/application/common/Server/thinkphp/library/think/db/Where.php new file mode 100644 index 00000000..9132e546 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/Where.php @@ -0,0 +1,178 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use ArrayAccess; + +class Where implements ArrayAccess +{ + /** + * 查询表达式 + * @var array + */ + protected $where = []; + + /** + * 是否需要增加括号 + * @var bool + */ + protected $enclose = false; + + /** + * 创建一个查询表达式 + * + * @param array $where 查询条件数组 + * @param bool $enclose 是否增加括号 + */ + public function __construct(array $where = [], $enclose = false) + { + $this->where = $where; + $this->enclose = $enclose; + } + + /** + * 设置是否添加括号 + * @access public + * @param bool $enclose + * @return $this + */ + public function enclose($enclose = true) + { + $this->enclose = $enclose; + return $this; + } + + /** + * 解析为Query对象可识别的查询条件数组 + * @access public + * @return array + */ + public function parse() + { + $where = []; + + foreach ($this->where as $key => $val) { + if ($val instanceof Expression) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } elseif (is_array($val)) { + $where[] = $this->parseItem($key, $val); + } else { + $where[] = [$key, '=', $val]; + } + } + + return $this->enclose ? [$where] : $where; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $field 查询字段 + * @param array $where 查询条件 + * @return array + */ + protected function parseItem($field, $where = []) + { + $op = $where[0]; + $condition = isset($where[1]) ? $where[1] : null; + + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($where, $field); + } elseif (is_null($condition)) { + if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) { + $where = [$field, 'NULL', '']; + } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } else { + $where = [$field, $op, $condition]; + } + + return $where; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->where[$name] = $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return isset($this->where[$name]) ? $this->where[$name] : null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + return isset($this->where[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->where[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->__set($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->__get($name); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/builder/Mysql.php b/Server/application/common/Server/thinkphp/library/think/db/builder/Mysql.php new file mode 100644 index 00000000..f7384b31 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/builder/Mysql.php @@ -0,0 +1,184 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; +use think\db\Query; +use think\Exception; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace + * @return string + */ + public function insertAll(Query $query, $dataSet, $replace = false) + { + $options = $query->getOptions(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = $this->connection->getTableFields($options['table']); + } else { + $allowFields = $options['field']; + } + + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + $fields = []; + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, $key, $exp, $value, $field) + { + if ($value instanceof Expression) { + $value = $value->getValue(); + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if(strpos($key, '->>') && false === strpos($key, '(')){ + // JSON字段支持 + list($field, $name) = explode('->>', $key, 2); + + return $this->parseKey($query, $field, true) . '->>\'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->>', '.', $name) . '\''; + } + elseif (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('->', $key, 2); + + return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + $key = '`' . $key . '`'; + } + + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + + $key = '`' . $table . '`.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'rand()'; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/builder/Pgsql.php b/Server/application/common/Server/thinkphp/library/think/db/builder/Pgsql.php new file mode 100644 index 00000000..742c7db3 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/builder/Pgsql.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, $limit) + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('->', $key); + $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'RANDOM()'; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/builder/Sqlite.php b/Server/application/common/Server/thinkphp/library/think/db/builder/Sqlite.php new file mode 100644 index 00000000..2b887ca8 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/builder/Sqlite.php @@ -0,0 +1,96 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + + /** + * limit + * @access public + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, $limit) + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/builder/Sqlsrv.php b/Server/application/common/Server/thinkphp/library/think/db/builder/Sqlsrv.php new file mode 100644 index 00000000..ef27aafa --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/builder/Sqlsrv.php @@ -0,0 +1,159 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; +use think\db\Query; +use think\Exception; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, $order) + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + $key = '[' . $key . ']'; + } + + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + + return $key; + } + + /** + * limit + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, $limit) + { + if (empty($limit)) { + return ''; + } + + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; + } else { + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; + } + + return 'WHERE ' . $limitStr; + } + + public function selectInsert(Query $query, $fields, $table) + { + $this->selectSql = $this->selectInsertSql; + + return parent::selectInsert($query, $fields, $table); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/connector/Mysql.php b/Server/application/common/Server/thinkphp/library/think/db/connector/Mysql.php new file mode 100644 index 00000000..cfd2ac72 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/connector/Mysql.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\db\Query; + +/** + * mysql数据库驱动 + */ +class Mysql extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Mysql'; + + /** + * 初始化 + * @access protected + * @return void + */ + protected function initialize() + { + // Point类型支持 + Query::extend('point', function ($query, $field, $value = null, $fun = 'GeomFromText', $type = 'POINT') { + if (!is_null($value)) { + $query->data($field, ['point', $value, $fun, $type]); + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + $query->setOption('point', $field); + } + + return $query; + }); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; + } + $dsn .= ';dbname=' . $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + + $sql = 'SHOW COLUMNS FROM ' . $tableName; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => 'NO' == $val['null'], + 'default' => $val['default'], + 'primary' => strtolower($val['key']) == 'pri', + 'autoinc' => strtolower($val['extra']) == 'auto_increment', + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + $pdo = $this->linkID->prepare("EXPLAIN " . $this->queryStr); + + foreach ($this->bind as $key => $val) { + // 占位符 + $param = is_int($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $pdo->bindValue($param, $val[0], $val[1]); + } else { + $result = $pdo->bindValue($param, $val); + } + } + + $pdo->execute(); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + $result = array_change_key_case($result); + + if (isset($result['extra'])) { + if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { + $this->log('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + } + } + + return $result; + } + + protected function supportSavepoint() + { + return true; + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa($xid) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + $this->linkID->exec("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa($xid) + { + $this->initConnect(true); + $this->linkID->exec("XA END '$xid'"); + $this->linkID->exec("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa($xid) + { + $this->initConnect(true); + $this->linkID->exec("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa($xid) + { + $this->initConnect(true); + $this->linkID->exec("XA ROLLBACK '$xid'"); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/connector/Pgsql.php b/Server/application/common/Server/thinkphp/library/think/db/connector/Pgsql.php new file mode 100644 index 00000000..ee9fca01 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/connector/Pgsql.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Connection +{ + protected $builder = '\\think\\db\\builder\\Pgsql'; + + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ';port=' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' !== $val['null']), + 'default' => $val['default'], + 'primary' => !empty($val['key']), + 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/connector/Sqlite.php b/Server/application/common/Server/thinkphp/library/think/db/connector/Sqlite.php new file mode 100644 index 00000000..5b9b3fa6 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/connector/Sqlite.php @@ -0,0 +1,108 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Sqlite'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlite:' . $config['database']; + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['name']] = [ + 'name' => $val['name'], + 'type' => $val['type'], + 'notnull' => 1 === $val['notnull'], + 'default' => $val['dflt_value'], + 'primary' => '1' == $val['pk'], + 'autoinc' => '1' == $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/connector/Sqlsrv.php b/Server/application/common/Server/thinkphp/library/think/db/connector/Sqlsrv.php new file mode 100644 index 00000000..123affb8 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/connector/Sqlsrv.php @@ -0,0 +1,235 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\db\Query; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Connection +{ + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ',' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $tableNames = explode('.', $tableName); + $tableName = isset($tableNames[1]) ? $tableNames[1] : $tableNames[0]; + + $sql = "SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ]; + } + } + + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + + // 调试开始 + $this->debug(true); + + $pdo = $this->linkID->query($sql); + + // 调试结束 + $this->debug(false, $sql); + + $result = $pdo->fetch(PDO::FETCH_ASSOC); + + if ($result) { + $info[$result['column_name']]['primary'] = true; + } + + return $this->fieldCase($info); + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * 得到某个列的数组 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(Query $query, $field, $key = '') + { + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + $guid = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $field); + + $result = Container::get('cache')->get($guid); + + if (false !== $result) { + return $result; + } + } + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if ('*' == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + + if (3 == $count) { + $column = $key2; + } elseif ($count < 3) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; + } + } + + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + + return $result; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/connector/pgsql.sql b/Server/application/common/Server/thinkphp/library/think/db/connector/pgsql.sql new file mode 100644 index 00000000..5a4442d0 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/connector/pgsql.sql @@ -0,0 +1,153 @@ +CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS +$BODY$ +DECLARE + v_type varchar; +BEGIN + IF a_type='int8' THEN + v_type:='bigint'; + ELSIF a_type='int4' THEN + v_type:='integer'; + ELSIF a_type='int2' THEN + v_type:='smallint'; + ELSIF a_type='bpchar' THEN + v_type:='char'; + ELSE + v_type:=a_type; + END IF; + RETURN v_type; +END; +$BODY$ +LANGUAGE PLPGSQL; + +CREATE TYPE "public"."tablestruct" AS ( + "fields_key_name" varchar(100), + "fields_name" VARCHAR(200), + "fields_type" VARCHAR(20), + "fields_length" BIGINT, + "fields_not_null" VARCHAR(10), + "fields_default" VARCHAR(500), + "fields_comment" VARCHAR(1000) +); + +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; + v_oid oid; + v_sql varchar; + v_rec RECORD; + v_key varchar; + v_conkey smallint[]; + v_pk varchar[]; + v_len smallint; + v_pos smallint := 1; +BEGIN + SELECT + pg_class.oid INTO v_oid + FROM + pg_class + INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) + WHERE + pg_class.relname=a_table_name; + IF NOT FOUND THEN + RETURN; + END IF; + + SELECT + pg_constraint.conkey INTO v_conkey + FROM + pg_constraint + INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid + WHERE + pg_class.relname = a_table_name + AND pg_constraint.contype = 'p'; + + v_len := array_length(v_conkey,1) + 1; + WHILE v_pos < v_len LOOP + SELECT + pg_attribute.attname INTO v_key + FROM pg_constraint + INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = pg_constraint.conkey [ v_conkey[v_pos] ] + INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid + WHERE pg_class.relname = a_table_name AND pg_constraint.contype = 'p'; + v_pk := array_append(v_pk,v_key); + + v_pos := v_pos + 1; + END LOOP; + + v_sql=' + SELECT + pg_attribute.attname AS fields_name, + pg_attribute.attnum AS fields_index, + pgsql_type(pg_type.typname::varchar) AS fields_type, + pg_attribute.atttypmod-4 as fields_length, + CASE WHEN pg_attribute.attnotnull THEN ''not null'' + ELSE '''' + END AS fields_not_null, + pg_attrdef.adsrc AS fields_default, + pg_description.description AS fields_comment + FROM + pg_attribute + INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid + LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum + LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum + WHERE + pg_attribute.attnum > 0 + AND attisdropped <> ''t'' + AND pg_class.oid = ' || v_oid || ' + ORDER BY pg_attribute.attnum' ; + + FOR v_rec IN EXECUTE v_sql LOOP + v_ret.fields_name=v_rec.fields_name; + v_ret.fields_type=v_rec.fields_type; + IF v_rec.fields_length > 0 THEN + v_ret.fields_length:=v_rec.fields_length; + ELSE + v_ret.fields_length:=NULL; + END IF; + v_ret.fields_not_null=v_rec.fields_not_null; + v_ret.fields_default=v_rec.fields_default; + v_ret.fields_comment=v_rec.fields_comment; + + v_ret.fields_key_name=''; + + v_len := array_length(v_pk,1) + 1; + v_pos := 1; + WHILE v_pos < v_len LOOP + IF v_rec.fields_name = v_pk[v_pos] THEN + v_ret.fields_key_name=v_pk[v_pos]; + EXIT; + END IF; + v_pos := v_pos + 1; + END LOOP; + + RETURN NEXT v_ret; + END LOOP; + RETURN ; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) +IS '获得表信息'; + +---重载一个函数 +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; +BEGIN + FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP + RETURN NEXT v_ret; + END LOOP; + RETURN; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) +IS '获得表信息'; \ No newline at end of file diff --git a/Server/application/common/Server/thinkphp/library/think/db/exception/BindParamException.php b/Server/application/common/Server/thinkphp/library/think/db/exception/BindParamException.php new file mode 100644 index 00000000..dce0c7bf --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/exception/BindParamException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct($message, $config, $sql, $bind, $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/exception/DataNotFoundException.php b/Server/application/common/Server/thinkphp/library/think/db/exception/DataNotFoundException.php new file mode 100644 index 00000000..883e333e --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @access public + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct($message, $table = '', array $config = []) + { + $this->message = $message; + $this->table = $table; + + $this->setData('Database Config', $config); + } + + /** + * 获取数据表名 + * @access public + * @return string + */ + public function getTable() + { + return $this->table; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/db/exception/ModelNotFoundException.php b/Server/application/common/Server/thinkphp/library/think/db/exception/ModelNotFoundException.php new file mode 100644 index 00000000..ae52baf3 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @access public + * @param string $message + * @param string $model + * @param array $config + */ + public function __construct($message, $model = '', array $config = []) + { + $this->message = $message; + $this->model = $model; + + $this->setData('Database Config', $config); + } + + /** + * 获取模型类名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/debug/Console.php b/Server/application/common/Server/thinkphp/library/think/debug/Console.php new file mode 100644 index 00000000..5cbaa0f2 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/debug/Console.php @@ -0,0 +1,156 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Container; +use think\Db; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Container::get('request'); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); + + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Container::get('debug')->getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console($type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['tabs']); + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m))); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', addslashes($m)); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/debug/Html.php b/Server/application/common/Server/thinkphp/library/think/debug/Html.php new file mode 100644 index 00000000..a123762e --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/debug/Html.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Container; +use think\Db; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'file' => '', + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Container::get('request'); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); + + // 页面Trace信息 + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Container::get('debug')->getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['file']; + return ob_get_clean(); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/ClassNotFoundException.php b/Server/application/common/Server/thinkphp/library/think/exception/ClassNotFoundException.php new file mode 100644 index 00000000..eb22e730 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/ClassNotFoundException.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ClassNotFoundException extends \RuntimeException +{ + protected $class; + public function __construct($message, $class = '') + { + $this->message = $message; + $this->class = $class; + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/DbException.php b/Server/application/common/Server/thinkphp/library/think/exception/DbException.php new file mode 100644 index 00000000..6baafb51 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/DbException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct($message, array $config = [], $sql = '', $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/ErrorException.php b/Server/application/common/Server/thinkphp/library/think/exception/ErrorException.php new file mode 100644 index 00000000..3143b8f7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/ErrorException.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * ThinkPHP错误异常 + * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 + * 除开从 think\Exception 继承的功能 + * 其他和PHP系统\ErrorException功能基本一样 + */ +class ErrorException extends Exception +{ + /** + * 用于保存错误级别 + * @var integer + */ + protected $severity; + + /** + * 错误异常构造函数 + * @access public + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + */ + public function __construct($severity, $message, $file, $line) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + } + + /** + * 获取错误级别 + * @access public + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/Handle.php b/Server/application/common/Server/thinkphp/library/think/exception/Handle.php new file mode 100644 index 00000000..02c85ec1 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/Handle.php @@ -0,0 +1,306 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\console\Output; +use think\Container; +use think\Response; + +class Handle +{ + protected $render; + protected $ignoreReport = [ + '\\think\\exception\\HttpException', + ]; + + public function setRender($render) + { + $this->render = $render; + } + + /** + * Report or log an exception. + * + * @access public + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (Container::get('app')->isDebug()) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'code' => $this->getCode($exception), + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + if (Container::get('app')->config('log.record_trace')) { + $log .= "\r\n" . $exception->getTraceAsString(); + } + + Container::get('log')->record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @access public + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + + if ($result) { + return $result; + } + } + + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @access public + * @param Output $output + * @param Exception $e + */ + public function renderForConsole(Output $output, Exception $e) + { + if (Container::get('app')->isDebug()) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + + $output->renderException($e); + } + + /** + * @access protected + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + $template = Container::get('app')->config('http_exception_template'); + + if (!Container::get('app')->isDebug() && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @access protected + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (Container::get('app')->isDebug()) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!Container::get('app')->config('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Container::get('app')->config('error_message'); + } + } + + //保留一层 + while (ob_get_level() > 1) { + ob_end_clean(); + } + + $data['echo'] = ob_get_clean(); + + ob_start(); + extract($data); + include Container::get('app')->config('exception_tmpl'); + + // 获取并清空缓存 + $content = ob_get_clean(); + $response = Response::create($content, 'html'); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param \Exception $exception + * @return string 错误信息 + */ + protected function getMessage(Exception $exception) + { + $message = $exception->getMessage(); + + if (PHP_SAPI == 'cli') { + return $message; + } + + $lang = Container::get('lang'); + + if (strpos($message, ':')) { + $name = strstr($message, ':', true); + $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message; + } elseif (strpos($message, ',')) { + $name = strstr($message, ',', true); + $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif ($lang->has($message)) { + $message = $lang->get($message); + } + + return $message; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @access protected + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()); + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @access protected + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + + return $data; + } + + /** + * 获取常量列表 + * @access private + * @return array 常量列表 + */ + private static function getConst() + { + $const = get_defined_constants(true); + + return isset($const['user']) ? $const['user'] : []; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/HttpException.php b/Server/application/common/Server/thinkphp/library/think/exception/HttpException.php new file mode 100644 index 00000000..01a27fc2 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/HttpException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/HttpResponseException.php b/Server/application/common/Server/thinkphp/library/think/exception/HttpResponseException.php new file mode 100644 index 00000000..52972867 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/HttpResponseException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Response; + +class HttpResponseException extends \RuntimeException +{ + /** + * @var Response + */ + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/PDOException.php b/Server/application/common/Server/thinkphp/library/think/exception/PDOException.php new file mode 100644 index 00000000..25240b68 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/PDOException.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) + { + $error = $exception->errorInfo; + + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + + parent::__construct($exception->getMessage(), $config, $sql, $code); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/RouteNotFoundException.php b/Server/application/common/Server/thinkphp/library/think/exception/RouteNotFoundException.php new file mode 100644 index 00000000..d22e3a63 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/TemplateNotFoundException.php b/Server/application/common/Server/thinkphp/library/think/exception/TemplateNotFoundException.php new file mode 100644 index 00000000..42020693 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct($message, $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate() + { + return $this->template; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/ThrowableError.php b/Server/application/common/Server/thinkphp/library/think/exception/ThrowableError.php new file mode 100644 index 00000000..87b6b9d7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/ThrowableError.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/exception/ValidateException.php b/Server/application/common/Server/thinkphp/library/think/exception/ValidateException.php new file mode 100644 index 00000000..81ddfe21 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/exception/ValidateException.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error, $code = 0) + { + $this->error = $error; + $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error; + $this->code = $code; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/App.php b/Server/application/common/Server/thinkphp/library/think/facade/App.php new file mode 100644 index 00000000..b375aa09 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/App.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\App + * @mixin \think\App + * @method \think\App bind(string $bind) static 绑定模块或者控制器 + * @method void initialize() static 初始化应用 + * @method void init(string $module='') static 初始化模块 + * @method \think\Response run() static 执行应用 + * @method \think\App dispatch(\think\route\Dispatch $dispatch) static 设置当前请求的调度信息 + * @method void log(mixed $log, string $type = 'info') static 记录调试信息 + * @method mixed config(string $name='') static 获取配置参数 + * @method \think\route\Dispatch routeCheck() static URL路由检测(根据PATH_INFO) + * @method \think\App routeMust(bool $must = false) static 设置应用的路由检测机制 + * @method \think\Model model(string $name = '', string $layer = 'model', bool $appendSuffix = false, string $common = 'common') static 实例化模型 + * @method object controller(string $name, string $layer = 'controller', bool $appendSuffix = false, string $empty = '') static 实例化控制器 + * @method \think\Validate validate(string $name = '', string $layer = 'validate', bool $appendSuffix = false, string $common = 'common') static 实例化验证器类 + * @method \think\db\Query db(mixed $config = [], mixed $name = false) static 数据库初始化 + * @method mixed action(string $url, $vars = [], $layer = 'controller', $appendSuffix = false) static 调用模块的操作方法 + * @method string parseClass(string $module, string $layer, string $name, bool $appendSuffix = false) static 解析应用类的类名 + * @method string version() static 获取框架版本 + * @method bool isDebug() static 是否为调试模式 + * @method string getModulePath() static 获取当前模块路径 + * @method void setModulePath(string $path) static 设置当前模块路径 + * @method string getRootPath() static 获取应用根目录 + * @method string getAppPath() static 获取应用类库目录 + * @method string getRuntimePath() static 获取应用运行时目录 + * @method string getThinkPath() static 获取核心框架目录 + * @method string getRoutePath() static 获取路由目录 + * @method string getConfigPath() static 获取应用配置目录 + * @method string getConfigExt() static 获取配置后缀 + * @method string setNamespace(string $namespace) static 设置应用类库命名空间 + * @method string getNamespace() static 获取应用类库命名空间 + * @method string getSuffix() static 是否启用类库后缀 + * @method float getBeginTime() static 获取应用开启时间 + * @method integer getBeginMem() static 获取应用初始内存占用 + * @method \think\Container container() static 获取容器实例 + */ +class App extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'app'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Build.php b/Server/application/common/Server/thinkphp/library/think/facade/Build.php new file mode 100644 index 00000000..c051bea1 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Build.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Build + * @mixin \think\Build + * @method void run(array $build = [], string $namespace = 'app', bool $suffix = false) static 根据传入的build资料创建目录和文件 + * @method void module(string $module = '', array $list = [], string $namespace = 'app', bool $suffix = false) static 创建模块 + */ +class Build extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'build'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Cache.php b/Server/application/common/Server/thinkphp/library/think/facade/Cache.php new file mode 100644 index 00000000..9743486e --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Cache.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cache + * @mixin \think\Cache + * @method \think\cache\Driver connect(array $options = [], mixed $name = false) static 连接缓存 + * @method \think\cache\Driver init(array $options = []) static 初始化缓存 + * @method \think\cache\Driver store(string $name = '') static 切换缓存类型 + * @method bool has(string $name) static 判断缓存是否存在 + * @method mixed get(string $name, mixed $default = false) static 读取缓存 + * @method mixed pull(string $name) static 读取缓存并删除 + * @method mixed set(string $name, mixed $value, int $expire = null) static 设置缓存 + * @method mixed remember(string $name, mixed $value, int $expire = null) static 如果不存在则写入缓存 + * @method mixed inc(string $name, int $step = 1) static 自增缓存(针对数值缓存) + * @method mixed dec(string $name, int $step = 1) static 自减缓存(针对数值缓存) + * @method bool rm(string $name) static 删除缓存 + * @method bool clear(string $tag = null) static 清除缓存 + * @method mixed tag(string $name, mixed $keys = null, bool $overlay = false) static 缓存标签 + * @method object handler() static 返回句柄对象,可执行其它高级方法 + */ +class Cache extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cache'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Config.php b/Server/application/common/Server/thinkphp/library/think/facade/Config.php new file mode 100644 index 00000000..824d2b6a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Config.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Config + * @mixin \think\Config + * @method array load(string $file, string $name = '') static 加载配置文件 + * @method bool has(string $name) static 检测配置是否存在 + * @method array pull(string $name) static 获取一级配置参数 + * @method mixed get(string $name,mixed $default = null) static 获取配置参数 + * @method array set(mixed $name, mixed $value = null) static 设置配置参数 + * @method array reset(string $name ='') static 重置配置参数 + * @method void remove(string $name = '') static 移除配置 + * @method void setYaconf(mixed $yaconf) static 设置开启Yaconf 或者指定配置文件名 + */ +class Config extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'config'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Cookie.php b/Server/application/common/Server/thinkphp/library/think/facade/Cookie.php new file mode 100644 index 00000000..4d7cea25 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Cookie.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cookie + * @mixin \think\Cookie + * @method void init(array $config = []) static 初始化 + * @method bool has(string $name,string $prefix = null) static 判断Cookie数据 + * @method mixed prefix(string $prefix = '') static 设置或者获取cookie作用域(前缀) + * @method mixed get(string $name,string $prefix = null) static Cookie获取 + * @method mixed set(string $name, mixed $value = null, mixed $option = null) static 设置Cookie + * @method void forever(string $name, mixed $value = null, mixed $option = null) static 永久保存Cookie数据 + * @method void delete(string $name, string $prefix = null) static Cookie删除 + * @method void clear($prefix = null) static Cookie清空 + */ +class Cookie extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cookie'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Debug.php b/Server/application/common/Server/thinkphp/library/think/facade/Debug.php new file mode 100644 index 00000000..df20086d --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Debug.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Debug + * @mixin \think\Debug + * @method void remark(string $name, mixed $value = '') static 记录时间(微秒)和内存使用情况 + * @method int getRangeTime(string $start, string $end, mixed $dec = 6) static 统计某个区间的时间(微秒)使用情况 + * @method int getUseTime(int $dec = 6) static 统计从开始到统计时的时间(微秒)使用情况 + * @method string getThroughputRate(string $start, string $end, mixed $dec = 6) static 获取当前访问的吞吐率情况 + * @method string getRangeMem(string $start, string $end, mixed $dec = 2) static 记录区间的内存使用情况 + * @method int getUseMem(int $dec = 2) static 统计从开始到统计时的内存使用情况 + * @method string getMemPeak(string $start, string $end, mixed $dec = 2) static 统计区间的内存峰值情况 + * @method mixed getFile(bool $detail = false) static 获取文件加载信息 + * @method mixed dump(mixed $var, bool $echo = true, string $label = null, int $flags = ENT_SUBSTITUTE) static 浏览器友好的变量输出 + */ +class Debug extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'debug'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Env.php b/Server/application/common/Server/thinkphp/library/think/facade/Env.php new file mode 100644 index 00000000..5d047244 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Env.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Env + * @mixin \think\Env + * @method void load(string $file) static 读取环境变量定义文件 + * @method mixed get(string $name = null, mixed $default = null) static 获取环境变量值 + * @method void set(mixed $env, string $value = null) static 设置环境变量值 + */ +class Env extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'env'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Hook.php b/Server/application/common/Server/thinkphp/library/think/facade/Hook.php new file mode 100644 index 00000000..e9e12083 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Hook.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Hook + * @mixin \think\Hook + * @method \think\Hook alias(mixed $name, mixed $behavior = null) static 指定行为标识 + * @method void add(string $tag, mixed $behavior, bool $first = false) static 动态添加行为扩展到某个标签 + * @method void import(array $tags, bool $recursive = true) static 批量导入插件 + * @method array get(string $tag = '') static 获取插件信息 + * @method mixed listen(string $tag, mixed $params = null, bool $once = false) static 监听标签的行为 + * @method mixed exec(mixed $class, mixed $params = null) static 执行行为 + */ +class Hook extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'hook'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Lang.php b/Server/application/common/Server/thinkphp/library/think/facade/Lang.php new file mode 100644 index 00000000..56c4777d --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Lang.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Lang + * @mixin \think\Lang + * @method mixed range($range = '') static 设定当前的语言 + * @method mixed set(mixed $name, string $value = null, string $range = '') static 设置语言定义 + * @method array load(mixed $file, string $range = '') static 加载语言定义 + * @method mixed get(string $name = null, array $vars = [], string $range = '') static 获取语言定义 + * @method mixed has(string $name, string $range = '') static 获取语言定义 + * @method string detect() static 自动侦测设置获取语言选择 + * @method void saveToCookie(string $lang = null) static 设置当前语言到Cookie + * @method void setLangDetectVar(string $var) static 设置语言自动侦测的变量 + * @method void setLangCookieVar(string $var) static 设置语言的cookie保存变量 + * @method void setAllowLangList(array $list) static 设置允许的语言列表 + */ +class Lang extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'lang'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Log.php b/Server/application/common/Server/thinkphp/library/think/facade/Log.php new file mode 100644 index 00000000..ae627a5c --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Log.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Log + * @mixin \think\Log + * @method \think\Log init(array $config = []) static 日志初始化 + * @method mixed getLog(string $type = '') static 获取日志信息 + * @method \think\Log record(mixed $msg, string $type = 'info', array $context = []) static 记录日志信息 + * @method \think\Log clear() static 清空日志信息 + * @method \think\Log key(string $key) static 当前日志记录的授权key + * @method \think\Log close() static 关闭本次请求日志写入 + * @method bool check(array $config) static 检查日志写入权限 + * @method bool save() static 保存调试信息 + * @method void write(mixed $msg, string $type = 'info', bool $force = false) static 实时写入日志信息 + * @method void log(string $level,mixed $message, array $context = []) static 记录日志信息 + * @method void emergency(mixed $message, array $context = []) static 记录emergency信息 + * @method void alert(mixed $message, array $context = []) static 记录alert信息 + * @method void critical(mixed $message, array $context = []) static 记录critical信息 + * @method void error(mixed $message, array $context = []) static 记录error信息 + * @method void warning(mixed $message, array $context = []) static 记录warning信息 + * @method void notice(mixed $message, array $context = []) static 记录notice信息 + * @method void info(mixed $message, array $context = []) static 记录info信息 + * @method void debug(mixed $message, array $context = []) static 记录debug信息 + * @method void sql(mixed $message, array $context = []) static 记录sql信息 + */ +class Log extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'log'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Middleware.php b/Server/application/common/Server/thinkphp/library/think/facade/Middleware.php new file mode 100644 index 00000000..5e4cac74 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Middleware + * @mixin \think\Middleware + * @method void import(array $middlewares = []) static 批量设置中间件 + * @method void add(mixed $middleware) static 添加中间件到队列 + * @method void unshift(mixed $middleware) static 添加中间件到队列开头 + * @method array all() static 获取中间件队列 + * @method \think\Response dispatch(\think\Request $request) static 执行中间件调度 + */ +class Middleware extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'middleware'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Request.php b/Server/application/common/Server/thinkphp/library/think/facade/Request.php new file mode 100644 index 00000000..0989253f --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Request.php @@ -0,0 +1,97 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Request + * @mixin \think\Request + * @method void hook(mixed $method, mixed $callback = null) static Hook 方法注入 + * @method \think\Request create(string $uri, string $method = 'GET', array $params = [], array $cookie = [], array $files = [], array $server = [], string $content = null) static 创建一个URL请求 + * @method mixed domain(bool $port = false) static 获取当前包含协议、端口的域名 + * @method mixed url(bool $domain = false) static 获取当前完整URL + * @method mixed baseUrl(bool $domain = false) static 获取当前URL + * @method mixed baseFile(bool $domain = false) static 获取当前执行的文件 + * @method mixed root(bool $domain = false) static 获取URL访问根地址 + * @method string rootUrl() static 获取URL访问根目录 + * @method string pathinfo() static 获取当前请求URL的pathinfo信息(含URL后缀) + * @method string path() static 获取当前请求URL的pathinfo信息(不含URL后缀) + * @method string ext() static 当前URL的访问后缀 + * @method float time(bool $float = false) static 获取当前请求的时间 + * @method mixed type() static 当前请求的资源类型 + * @method void mimeType(mixed $type, string $val = '') static 设置资源类型 + * @method string method(bool $method = false) static 当前的请求类型 + * @method bool isGet() static 是否为GET请求 + * @method bool isPost() static 是否为POST请求 + * @method bool isPut() static 是否为PUT请求 + * @method bool isDelete() static 是否为DELTE请求 + * @method bool isHead() static 是否为HEAD请求 + * @method bool isPatch() static 是否为PATCH请求 + * @method bool isOptions() static 是否为OPTIONS请求 + * @method bool isCli() static 是否为cli + * @method bool isCgi() static 是否为cgi + * @method mixed param(string $name = '', mixed $default = null, mixed $filter = '') static 获取当前请求的参数 + * @method mixed route(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取路由参数 + * @method mixed get(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取GET参数 + * @method mixed post(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取POST参数 + * @method mixed put(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取PUT参数 + * @method mixed delete(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取DELETE参数 + * @method mixed patch(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取PATCH参数 + * @method mixed request(string $name = '', mixed $default = null, mixed $filter = '') static 获取request变量 + * @method mixed session(string $name = '', mixed $default = null, mixed $filter = '') static 获取session数据 + * @method mixed cookie(string $name = '', mixed $default = null, mixed $filter = '') static 获取cookie参数 + * @method mixed server(string $name = '', mixed $default = null, mixed $filter = '') static 获取server参数 + * @method mixed env(string $name = '', mixed $default = null, mixed $filter = '') static 获取环境变量 + * @method mixed file(string $name = '') static 获取上传的文件信息 + * @method mixed header(string $name = '', mixed $default = null) static 设置或者获取当前的Header + * @method mixed input(array $data,mixed $name = '', mixed $default = null, mixed $filter = '') static 获取变量 支持过滤和默认值 + * @method mixed filter(mixed $filter = null) static 设置或获取当前的过滤规则 + * @method mixed has(string $name, string $type = 'param', bool $checkEmpty = false) static 是否存在某个请求参数 + * @method mixed only(mixed $name, string $type = 'param') static 获取指定的参数 + * @method mixed except(mixed $name, string $type = 'param') static 排除指定参数获取 + * @method bool isSsl() static 当前是否ssl + * @method bool isAjax(bool $ajax = false) static 当前是否Ajax请求 + * @method bool isPjax(bool $pjax = false) static 当前是否Pjax请求 + * @method mixed ip(int $type = 0, bool $adv = true) static 获取客户端IP地址 + * @method bool isMobile() static 检测是否使用手机访问 + * @method string scheme() static 当前URL地址中的scheme参数 + * @method string query() static 当前请求URL地址中的query参数 + * @method string host(bool $stric = false) static 当前请求的host + * @method string port() static 当前请求URL地址中的port参数 + * @method string protocol() static 当前请求 SERVER_PROTOCOL + * @method string remotePort() static 当前请求 REMOTE_PORT + * @method string contentType() static 当前请求 HTTP_CONTENT_TYPE + * @method array routeInfo() static 获取当前请求的路由信息 + * @method array dispatch() static 获取当前请求的调度信息 + * @method string module() static 获取当前的模块名 + * @method string controller(bool $convert = false) static 获取当前的控制器名 + * @method string action(bool $convert = false) static 获取当前的操作名 + * @method string langset() static 获取当前的语言 + * @method string getContent() static 设置或者获取当前请求的content + * @method string getInput() static 获取当前请求的php://input + * @method string token(string $name = '__token__', mixed $type = 'md5') static 生成请求令牌 + * @method string cache(string $key, mixed $expire = null, array $except = [], string $tag = null) static 设置当前地址的请求缓存 + * @method string getCache() static 读取请求缓存设置 + */ +class Request extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'request'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Response.php b/Server/application/common/Server/thinkphp/library/think/facade/Response.php new file mode 100644 index 00000000..d7de142f --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Response.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Response + * @mixin \think\Response + * @method \think\response create(mixed $data = '', string $type = '', int $code = 200, array $header = [], array $options = []) static 创建Response对象 + * @method void send() static 发送数据到客户端 + * @method \think\Response options(mixed $options = []) static 输出的参数 + * @method \think\Response data(mixed $data) static 输出数据设置 + * @method \think\Response header(mixed $name, string $value = null) static 设置响应头 + * @method \think\Response content(mixed $content) static 设置页面输出内容 + * @method \think\Response code(int $code) static 发送HTTP状态 + * @method \think\Response lastModified(string $time) static LastModified + * @method \think\Response expires(string $time) static expires + * @method \think\Response eTag(string $eTag) static eTag + * @method \think\Response cacheControl(string $cache) static 页面缓存控制 + * @method \think\Response contentType(string $contentType, string $charset = 'utf-8') static 页面输出类型 + * @method mixed getHeader(string $name) static 获取头部信息 + * @method mixed getData() static 获取原始数据 + * @method mixed getContent() static 获取输出数据 + * @method int getCode() static 获取状态码 + */ +class Response extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'response'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Route.php b/Server/application/common/Server/thinkphp/library/think/facade/Route.php new file mode 100644 index 00000000..6457ba4b --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Route.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Route + * @mixin \think\Route + * @method \think\route\Domain domain(mixed $name, mixed $rule = '', array $option = [], array $pattern = []) static 注册域名路由 + * @method \think\Route pattern(mixed $name, string $rule = '') static 注册变量规则 + * @method \think\Route option(mixed $name, mixed $value = '') static 注册路由参数 + * @method \think\Route bind(string $bind) static 设置路由绑定 + * @method mixed getBind(string $bind) static 读取路由绑定 + * @method \think\Route name(string $name) static 设置当前路由标识 + * @method mixed getName(string $name) static 读取路由标识 + * @method void setName(string $name) static 批量导入路由标识 + * @method void import(array $rules, string $type = '*') static 导入配置文件的路由规则 + * @method \think\route\RuleItem rule(string $rule, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由规则 + * @method void rules(array $rules, string $method = '*', array $option = [], array $pattern = []) static 批量注册路由规则 + * @method \think\route\RuleGroup group(string|array $name, array|\Closure $route, array $method = '*', array $option = [], array $pattern = []) static 注册路由分组 + * @method \think\route\RuleItem any(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem get(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem post(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem put(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem delete(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem patch(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\Resource resource(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册资源路由 + * @method \think\Route controller(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册控制器路由 + * @method \think\Route alias(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册别名路由 + * @method \think\Route setMethodPrefix(mixed $method, string $prefix = '') static 设置不同请求类型下面的方法前缀 + * @method \think\Route rest(string $name, array $resource = []) static rest方法定义和修改 + * @method \think\Route\RuleItem miss(string $route, string $method = '*', array $option = []) static 注册未匹配路由规则后的处理 + * @method \think\Route\RuleItem auto(string $route) static 注册一个自动解析的URL路由 + * @method \think\Route\Dispatch check(string $url, string $depr = '/', bool $must = false, bool $completeMatch = false) static 检测URL路由 + */ +class Route extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'route'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Session.php b/Server/application/common/Server/thinkphp/library/think/facade/Session.php new file mode 100644 index 00000000..fb9206af --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Session.php @@ -0,0 +1,46 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Session + * @mixin \think\Session + * @method void init(array $config = []) static session初始化 + * @method bool has(string $name,string $prefix = null) static 判断session数据 + * @method mixed prefix(string $prefix = '') static 设置或者获取session作用域(前缀) + * @method mixed get(string $name = '',string $prefix = null) static session获取 + * @method mixed pull(string $name,string $prefix = null) static session获取并删除 + * @method void push(string $key, mixed $value) static 添加数据到一个session数组 + * @method void set(string $name, mixed $value , string $prefix = null) static 设置session数据 + * @method void flash(string $name, mixed $value = null) static session设置 下一次请求有效 + * @method void flush() static 清空当前请求的session数据 + * @method void delete(string $name, string $prefix = null) static 删除session数据 + * @method void clear($prefix = null) static 清空session数据 + * @method void start() static 启动session + * @method void destroy() static 销毁session + * @method void pause() static 暂停session + * @method void regenerate(bool $delete = false) static 重新生成session_id + */ +class Session extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'session'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Template.php b/Server/application/common/Server/thinkphp/library/think/facade/Template.php new file mode 100644 index 00000000..f91b1182 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Template.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Template + * @mixin \think\Template + * @method void assign(mixed $name, mixed $value = '') static 模板变量赋值 + * @method mixed get(string $name = '') static 获取模板变量 + * @method void fetch(string $template, array $vars = [], array $config = []) static 渲染模板文件 + * @method void display(string $content, array $vars = [], array $config = []) static 渲染模板内容 + * @method mixed layout(string $name, string $replace = '') static 设置模板布局 + */ +class Template extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'template'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Url.php b/Server/application/common/Server/thinkphp/library/think/facade/Url.php new file mode 100644 index 00000000..639591ac --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Url.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Url + * @mixin \think\Url + * @method string build(string $url = '', mixed $vars = '', mixed $suffix = true, mixed $domain = false) static URL生成 支持路由反射 + * @method void root(string $root) static 指定当前生成URL地址的root + */ +class Url extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'url'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/Validate.php b/Server/application/common/Server/thinkphp/library/think/facade/Validate.php new file mode 100644 index 00000000..a6eec23e --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/Validate.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Validate + * @mixin \think\Validate + * @method \think\Validate make(array $rules = [], array $message = [], array $field = []) static 创建一个验证器类 + * @method \think\Validate rule(mixed $name, mixed $rule = '') static 添加字段验证规则 + * @method void extend(string $type, mixed $callback = null) static 注册扩展验证(类型)规则 + * @method void setTypeMsg(mixed $type, string $msg = null) static 设置验证规则的默认提示信息 + * @method \think\Validate message(mixed $name, string $message = '') static 设置提示信息 + * @method \think\Validate scene(string $name) static 设置验证场景 + * @method bool hasScene(string $name) static 判断是否存在某个验证场景 + * @method \think\Validate batch(bool $batch = true) static 设置批量验证 + * @method \think\Validate only(array $fields) static 指定需要验证的字段列表 + * @method \think\Validate remove(mixed $field, mixed $rule = true) static 移除某个字段的验证规则 + * @method \think\Validate append(mixed $field, mixed $rule = null) static 追加某个字段的验证规则 + * @method bool confirm(mixed $value, mixed $rule, array $data = [], string $field = '') static 验证是否和某个字段的值一致 + * @method bool different(mixed $value, mixed $rule, array $data = []) static 验证是否和某个字段的值是否不同 + * @method bool egt(mixed $value, mixed $rule, array $data = []) static 验证是否大于等于某个值 + * @method bool gt(mixed $value, mixed $rule, array $data = []) static 验证是否大于某个值 + * @method bool elt(mixed $value, mixed $rule, array $data = []) static 验证是否小于等于某个值 + * @method bool lt(mixed $value, mixed $rule, array $data = []) static 验证是否小于某个值 + * @method bool eq(mixed $value, mixed $rule) static 验证是否等于某个值 + * @method bool must(mixed $value, mixed $rule) static 必须验证 + * @method bool is(mixed $value, mixed $rule, array $data = []) static 验证字段值是否为有效格式 + * @method bool ip(mixed $value, mixed $rule) static 验证是否有效IP + * @method bool requireIf(mixed $value, mixed $rule) static 验证某个字段等于某个值的时候必须 + * @method bool requireCallback(mixed $value, mixed $rule,array $data) static 通过回调方法验证某个字段是否必须 + * @method bool requireWith(mixed $value, mixed $rule, array $data) static 验证某个字段有值的情况下必须 + * @method bool filter(mixed $value, mixed $rule) static 使用filter_var方式验证 + * @method bool in(mixed $value, mixed $rule) static 验证是否在范围内 + * @method bool notIn(mixed $value, mixed $rule) static 验证是否不在范围内 + * @method bool between(mixed $value, mixed $rule) static between验证数据 + * @method bool notBetween(mixed $value, mixed $rule) static 使用notbetween验证数据 + * @method bool length(mixed $value, mixed $rule) static 验证数据长度 + * @method bool max(mixed $value, mixed $rule) static 验证数据最大长度 + * @method bool min(mixed $value, mixed $rule) static 验证数据最小长度 + * @method bool after(mixed $value, mixed $rule) static 验证日期 + * @method bool before(mixed $value, mixed $rule) static 验证日期 + * @method bool expire(mixed $value, mixed $rule) static 验证有效期 + * @method bool allowIp(mixed $value, mixed $rule) static 验证IP许可 + * @method bool denyIp(mixed $value, mixed $rule) static 验证IP禁用 + * @method bool regex(mixed $value, mixed $rule) static 使用正则验证数据 + * @method bool token(mixed $value, mixed $rule) static 验证表单令牌 + * @method bool dateFormat(mixed $value, mixed $rule) static 验证时间和日期是否符合指定格式 + * @method bool unique(mixed $value, mixed $rule, array $data = [], string $field = '') static 验证是否唯一 + * @method bool check(array $data, mixed $rules = [], string $scene = '') static 数据自动验证 + * @method mixed getError(mixed $value, mixed $rule) static 获取错误信息 + */ +class Validate extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'validate'; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/facade/View.php b/Server/application/common/Server/thinkphp/library/think/facade/View.php new file mode 100644 index 00000000..08433917 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/facade/View.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\View + * @mixin \think\View + * @method \think\View init(mixed $engine = [], array $replace = []) static 初始化 + * @method \think\View share(mixed $name, mixed $value = '') static 模板变量静态赋值 + * @method \think\View assign(mixed $name, mixed $value = '') static 模板变量赋值 + * @method \think\View config(mixed $name, mixed $value = '') static 配置模板引擎 + * @method \think\View exists(mixed $name) static 检查模板是否存在 + * @method \think\View filter(Callable $filter) static 视图内容过滤 + * @method \think\View engine(mixed $engine = []) static 设置当前模板解析的引擎 + * @method string fetch(string $template = '', array $vars = [], array $config = [], bool $renderContent = false) static 解析和获取模板内容 + * @method string display(string $content = '', array $vars = [], array $config = []) static 渲染内容输出 + */ +class View extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'view'; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/log/driver/File.php b/Server/application/common/Server/thinkphp/library/think/log/driver/File.php new file mode 100644 index 00000000..3f6522d1 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/log/driver/File.php @@ -0,0 +1,287 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * 本地化调试输出到文件 + */ +class File +{ + protected $config = [ + 'time_format' => 'c', + 'single' => false, + 'file_size' => 2097152, + 'path' => '', + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + ]; + + protected $app; + + // 实例化并传入参数 + public function __construct(App $app, $config = []) + { + $this->app = $app; + + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + + if (empty($this->config['path'])) { + $this->config['path'] = $this->app->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR; + } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $msg = is_array($msg) ? implode(PHP_EOL, $msg) : $msg; + if (PHP_SAPI == 'cli') { + $info['msg'] = $msg; + $info['type'] = $type; + } else { + $info[$type] = $msg; + } + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() + { + if ($this->config['max_files']) { + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } + + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $destination = $this->config['path'] . $name . $cli . '.log'; + } else { + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + } elseif ($this->config['max_files']) { + $name = date('Ymd'); + } else { + $name = date('d'); + } + + return $path . DIRECTORY_SEPARATOR . $name . '_' . $type . $cli . '.log'; + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode(PHP_EOL, $info); + + $message = "[{$now}]" . $message . PHP_EOL; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $requestInfo = [ + 'ip' => $this->app['request']->ip(), + 'method' => $this->app['request']->method(), + 'host' => $this->app['request']->host(), + 'uri' => $this->app['request']->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; + } + + array_unshift($info, "---------------------------------------------------------------" . PHP_EOL . "\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode(PHP_EOL, $info) . PHP_EOL; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if ($this->app->isDebug() && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/log/driver/Socket.php b/Server/application/common/Server/thinkphp/library/think/log/driver/Socket.php new file mode 100644 index 00000000..5e4f8bfd --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/log/driver/Socket.php @@ -0,0 +1,279 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket +{ + public $port = 1116; //SocketLog 服务的http的端口号 + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + //输出到浏览器默认展开的日志级别 + 'expand_level' => ['debug'], + ]; + + protected $css = [ + 'sql' => 'color:#009bb4;', + 'sql_warn' => 'color:#009bb4;font-size:14px;', + 'error' => 'color:#f4006b;font-size:14px;', + 'page' => 'color:#40e2ff;background:#171717;', + 'big' => 'font-size:20px;color:red;', + ]; + + protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + protected $app; + + /** + * 架构函数 + * @access public + * @param array $config 缓存参数 + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + if (!$this->check()) { + return false; + } + + $trace = []; + + if ($this->app->isDebug()) { + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; + } + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => in_array($type, $this->config['expand_level']) ? 'group' : 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => isset($this->css[$type]) ? $this->css[$type] : '', + ]; + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + $trace[] = [ + 'type' => 'log', + 'msg' => $msg, + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + if ($this->config['show_included_files']) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ file ]', + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + + $tabid = $this->getClientArg('tabid'); + + if (!$client_id = $this->getClientArg('client_id')) { + $client_id = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; + $this->sendToClient($tabid, $client_id, $trace, $force_client_id); + } + } else { + $this->sendToClient($tabid, $client_id, $trace, ''); + } + + return true; + } + + /** + * 发送给指定客户端 + * @access protected + * @author Zjmainstay + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id + */ + protected function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, + ]; + + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + + $this->send($this->config['host'], $msg, $address); + } + + protected function check() + { + $tabid = $this->getClientArg('tabid'); + + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + + //用户认证 + $allow_client_ids = $this->config['allow_client_ids']; + + if (!empty($allow_client_ids)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $client_id = $this->getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + + return true; + } + + protected function getClientArg($name) + { + static $args = []; + + $key = 'HTTP_USER_AGENT'; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $key = 'HTTP_SOCKETLOG'; + } + + if (!isset($_SERVER[$key])) { + return; + } + + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = ['tabid' => null]; + return; + } + parse_str($match[1], $args); + } + + if (isset($args[$name])) { + return $args[$name]; + } + + return; + } + + /** + * @access protected + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $this->port . $address; + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + + $headers = [ + "Content-Type: application/json;charset=UTF-8", + ]; + + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + + return curl_exec($ch); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/Collection.php b/Server/application/common/Server/thinkphp/library/think/model/Collection.php new file mode 100644 index 00000000..fc0967cf --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/Collection.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + if (!$this->isEmpty()) { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + } + + return $this; + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + */ + public function bindAttr($relation, array $attrs = []) + { + $this->each(function (Model $model) use ($relation, $attrs) { + $model->bindAttr($relation, $attrs); + }); + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model && $model->append($append, $override); + }); + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + $this->each(function ($model) use ($name, $callback) { + /** @var Model $model */ + $model && $model->withAttribute($name, $callback); + }); + + return $this; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/Pivot.php b/Server/application/common/Server/thinkphp/library/think/model/Pivot.php new file mode 100644 index 00000000..a3a395e3 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/Pivot.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Model; + +class Pivot extends Model +{ + + /** @var Model */ + public $parent; + + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct($data = [], Model $parent = null, $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/Relation.php b/Server/application/common/Server/thinkphp/library/think/model/Relation.php new file mode 100644 index 00000000..ac6dd4cf --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/Relation.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\db\Query; +use think\Exception; +use think\Model; + +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation +{ + // 父模型对象 + protected $parent; + /** @var Model 当前关联的模型类 */ + protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; + // 关联表外键 + protected $foreignKey; + // 关联表主键 + protected $localKey; + // 基础查询 + protected $baseQuery; + // 是否为自关联 + protected $selfRelation; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + return $this->query->getModel(); + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @return mixed + */ + protected function resultSetBuild($resultSet) + { + return (new $this->model)->toCollection($resultSet); + } + + protected function getQueryFields($model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; + } + + protected function getQueryWhere(&$where, $relation) + { + foreach ($where as $key => &$val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } elseif (isset($val[0]) && false === strpos($val[0], '.')) { + $val[0] = $relation . '.' . $val[0]; + } + } + } + + /** + * 更新数据 + * @access public + * @param array $data 更新数据 + * @return integer|string + */ + public function update(array $data = []) + { + return $this->query->update($data); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + return $this->query->delete($data); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query->getModel(), $method], $args); + + return $result === $this->query && !in_array(strtolower($method), ['fetchsql', 'fetchpdo']) ? $this : $result; + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/concern/Attribute.php b/Server/application/common/Server/thinkphp/library/think/model/concern/Attribute.php new file mode 100644 index 00000000..66627b38 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/concern/Attribute.php @@ -0,0 +1,656 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use InvalidArgumentException; +use think\db\Expression; +use think\Exception; +use think\Loader; +use think\model\Relation; + +trait Attribute +{ + /** + * 数据表主键 复合主键使用数组定义 + * @var string|array + */ + protected $pk = 'id'; + + /** + * 数据表字段信息 留空则自动获取 + * @var array + */ + protected $field = []; + + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + + /** + * JSON数据取出是否需要转换为数组 + * @var bool + */ + protected $jsonAssoc = false; + + /** + * JSON数据表字段类型 + * @var array + */ + protected $jsonType = []; + + /** + * 数据表废弃字段 + * @var array + */ + protected $disuse = []; + + /** + * 数据表只读字段 + * @var array + */ + protected $readonly = []; + + /** + * 数据表字段类型 + * @var array + */ + protected $type = []; + + /** + * 当前模型数据 + * @var array + */ + private $data = []; + + /** + * 修改器执行记录 + * @var array + */ + private $set = []; + + /** + * 原始数据 + * @var array + */ + private $origin = []; + + /** + * 动态获取器 + * @var array + */ + private $withAttr = []; + + /** + * 获取模型对象的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk($key) + { + $pk = $this->getPk(); + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + + return false; + } + + /** + * 获取模型对象的主键值 + * @access public + * @return integer + */ + public function getKey() + { + $pk = $this->getPk(); + if (is_string($pk) && array_key_exists($pk, $this->data)) { + return $this->data[$pk]; + } + + return; + } + + /** + * 设置允许写入的字段 + * @access public + * @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段 + * @return $this + */ + public function allowField($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + + $this->field = $field; + + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param array|string $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + + $this->readonly = $field; + + return $this; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据或者属性名 + * @param mixed $value 值 + * @return $this + */ + public function data($data, $value = null) + { + if (is_string($data)) { + $this->data[$data] = $value; + return $this; + } + + // 清空数据 + $this->data = []; + + if (is_object($data)) { + $data = get_object_vars($data); + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + } + + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } elseif (is_array($value)) { + foreach ($value as $name) { + if (isset($data[$name])) { + $this->data[$name] = $data[$name]; + } + } + } else { + $this->data = $data; + } + + return $this; + } + + /** + * 批量设置数据对象值 + * @access public + * @param mixed $data 数据 + * @param bool $set 是否需要进行数据处理 + * @return $this + */ + public function appendData($data, $set = false) + { + if ($set) { + // 进行数据处理 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } else { + if (is_object($data)) { + $data = get_object_vars($data); + } + + $this->data = array_merge($this->data, $data); + } + + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回null + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + */ + public function getOrigin($name = null) + { + if (is_null($name)) { + return $this->origin; + } + return array_key_exists($name, $this->origin) ? $this->origin[$name] : null; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData($name = null) + { + if (is_null($name)) { + return $this->data; + } elseif (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + if ($this->force) { + $data = $this->data; + } else { + $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + + return is_object($a) || $a != $b ? 1 : 0; + }); + } + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + + /** + * 修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return void + */ + public function setAttr($name, $value, $data = []) + { + if (isset($this->set[$name])) { + return; + } + + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp($name); + } else { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + + if (method_exists($this, $method)) { + $origin = $this->data; + $value = $this->$method($value, array_merge($this->data, $data)); + + $this->set[$name] = true; + if (is_null($value) && $origin !== $this->data) { + return; + } + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + } + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $auto; + + return $this; + } + + /** + * 自动写入时间戳 + * @access protected + * @param string $name 时间戳字段 + * @return mixed + */ + protected function autoWriteTimestamp($name) + { + if (isset($this->type[$name])) { + $type = $this->type[$name]; + + if (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'datetime': + case 'date': + $value = $this->formatDateTime('Y-m-d H:i:s.u'); + break; + case 'timestamp': + case 'integer': + default: + $value = time(); + break; + } + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ])) { + $value = $this->formatDateTime('Y-m-d H:i:s.u'); + } else { + $value = time(); + } + + return $value; + } + + /** + * 数据写入 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if ($value instanceof Expression) { + return $value; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime('Y-m-d H:i:s.u', $value); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + } + + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @param array $item 数据 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr($name, &$item = null) + { + try { + $notFound = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $notFound = true; + $value = null; + } + + // 检测属性获取器 + $fieldName = Loader::parseName($name); + $method = 'get' . Loader::parseName($name, 1) . 'Attr'; + + if (isset($this->withAttr[$fieldName])) { + if ($notFound && $relation = $this->isRelationAttr($name)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + } + + $closure = $this->withAttr[$fieldName]; + $value = $closure($value, $this->data); + } elseif (method_exists($this, $method)) { + if ($notFound && $relation = $this->isRelationAttr($name)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + } + + $value = $this->$method($value, $this->data); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$name]); + } elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ])) { + $value = $this->formatDateTime($this->dateFormat, $value); + } else { + $value = $this->formatDateTime($this->dateFormat, $value, true); + } + } elseif ($notFound) { + $value = $this->getRelationAttribute($name, $item); + } + + return $value; + } + + /** + * 获取关联属性值 + * @access protected + * @param string $name 属性名 + * @param array $item 数据 + * @return mixed + */ + protected function getRelationAttribute($name, &$item) + { + $relation = $this->isRelationAttr($name); + + if ($relation) { + $modelRelation = $this->$relation(); + if ($modelRelation instanceof Relation) { + $value = $this->getRelationData($modelRelation); + + if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + + return false; + } + + // 保存关联对象值 + $this->relation[$name] = $value; + + return $value; + } + } + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 数据读取 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value, true); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + + return $value; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttribute($name, $callback = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $key = Loader::parseName($key); + + $this->withAttr[$key] = $val; + } + } else { + $name = Loader::parseName($name); + + $this->withAttr[$name] = $callback; + } + + return $this; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/concern/Conversion.php b/Server/application/common/Server/thinkphp/library/think/model/concern/Conversion.php new file mode 100644 index 00000000..de4db931 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/concern/Conversion.php @@ -0,0 +1,273 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Collection; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Collection as ModelCollection; + +/** + * 模型数据转换处理 + */ +trait Conversion +{ + /** + * 数据输出显示的属性 + * @var array + */ + protected $visible = []; + + /** + * 数据输出隐藏的属性 + * @var array + */ + protected $hidden = []; + + /** + * 数据输出需要追加的属性 + * @var array + */ + protected $append = []; + + /** + * 数据集对象名 + * @var string + */ + protected $resultSetType; + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append(array $append = [], $override = false) + { + $this->append = $override ? $append : array_merge($this->append, $append); + + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $attr 关联属性 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($attr, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + + $relation = Loader::parseName($attr, 1, false); + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $this->data[$key] = $model->getAttr($attr); + } + } + } + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden(array $hidden = [], $override = false) + { + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible(array $visible = [], $override = false) + { + $this->visible = $override ? $visible : array_merge($this->visible, $visible); + + return $this; + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray() + { + $item = []; + $hasVisible = false; + + foreach ($this->visible as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + list($relation, $name) = explode('.', $val); + $this->visible[$relation][] = $name; + } else { + $this->visible[$val] = true; + $hasVisible = true; + } + unset($this->visible[$key]); + } + } + + foreach ($this->hidden as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + list($relation, $name) = explode('.', $val); + $this->hidden[$relation][] = $name; + } else { + $this->hidden[$val] = true; + } + unset($this->hidden[$key]); + } + } + + // 合并关联数据 + $data = array_merge($this->data, $this->relation); + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + if (isset($this->visible[$key]) && is_array($this->visible[$key])) { + $val->visible($this->visible[$key]); + } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { + $val->hidden($this->hidden[$key]); + } + // 关联模型对象 + if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { + $item[$key] = $val->toArray(); + } + } elseif (isset($this->visible[$key])) { + $item[$key] = $this->getAttr($key); + } elseif (!isset($this->hidden[$key]) && !$hasVisible) { + $item[$key] = $this->getAttr($key); + } + } + + // 追加属性(必须定义获取器) + if (!empty($this->append)) { + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getRelation($key); + + if (!$relation) { + $relation = $this->getAttr($key); + if ($relation) { + $relation->visible($name); + } + } + + $item[$key] = $relation ? $relation->append($name)->toArray() : []; + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getRelation($key); + + if (!$relation) { + $relation = $this->getAttr($key); + if ($relation) { + $relation->visible([$attr]); + } + } + + $item[$key] = $relation ? $relation->append([$attr])->toArray() : []; + } else { + $item[$name] = $this->getAttr($name, $item); + } + } + } + + return $item; + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 + * @return Collection + */ + public function toCollection($collection, $resultSetType = null) + { + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); + } else { + $collection = new ModelCollection($collection); + } + + return $collection; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/concern/ModelEvent.php b/Server/application/common/Server/thinkphp/library/think/model/concern/ModelEvent.php new file mode 100644 index 00000000..3a874846 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/concern/ModelEvent.php @@ -0,0 +1,238 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Container; +use think\Loader; + +/** + * 模型事件处理 + */ +trait ModelEvent +{ + /** + * 模型回调 + * @var array + */ + private static $event = []; + + /** + * 模型事件观察 + * @var array + */ + protected static $observe = ['before_write', 'after_write', 'before_insert', 'after_insert', 'before_update', 'after_update', 'before_delete', 'after_delete', 'before_restore', 'after_restore']; + + /** + * 绑定模型事件观察者类 + * @var array + */ + protected $observerClass; + + /** + * 是否需要事件响应 + * @var bool + */ + private $withEvent = true; + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 + * @return void + */ + public static function event($event, $callback, $override = false) + { + $class = static::class; + + if ($override) { + self::$event[$class][$event] = []; + } + + self::$event[$class][$event][] = $callback; + } + + /** + * 清除回调方法 + * @access public + * @return void + */ + public static function flushEvent() + { + self::$event[static::class] = []; + } + + /** + * 注册一个模型观察者 + * + * @param object|string $class + * @return void + */ + public static function observe($class) + { + self::flushEvent(); + + foreach (static::$observe as $event) { + $eventFuncName = Loader::parseName($event, 1, false); + + if (method_exists($class, $eventFuncName)) { + static::event($event, [$class, $eventFuncName]); + } + } + } + + /** + * 当前操作的事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent($event) + { + $this->withEvent = $event; + return $this; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @return bool + */ + protected function trigger($event) + { + $class = static::class; + + if ($this->withEvent && isset(self::$event[$class][$event])) { + foreach (self::$event[$class][$event] as $callback) { + $result = Container::getInstance()->invoke($callback, [$this]); + + if (false === $result) { + return false; + } + } + } + + return true; + } + + /** + * 模型before_insert事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + /** + * 模型after_insert事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + /** + * 模型before_update事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + /** + * 模型after_update事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + /** + * 模型before_write事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + /** + * 模型after_write事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + /** + * 模型before_delete事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + /** + * 模型after_delete事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + + /** + * 模型before_restore事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeRestore($callback, $override = false) + { + self::event('before_restore', $callback, $override); + } + + /** + * 模型after_restore事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterRestore($callback, $override = false) + { + self::event('after_restore', $callback, $override); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/concern/RelationShip.php b/Server/application/common/Server/thinkphp/library/think/model/concern/RelationShip.php new file mode 100644 index 00000000..48579b70 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/concern/RelationShip.php @@ -0,0 +1,697 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Collection; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; + +/** + * 模型关联处理 + */ +trait RelationShip +{ + /** + * 父关联模型对象 + * @var object + */ + private $parent; + + /** + * 模型关联数据 + * @var array + */ + private $relation = []; + + /** + * 关联写入定义信息 + * @var array + */ + private $together; + + /** + * 关联自动写入信息 + * @var array + */ + protected $relationWrite; + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } + return; + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setRelation($name, $value, $data = []) + { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } + + $this->relation[$name] = $value; + + return $this; + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr($relation, array $attrs = []) + { + $relation = $this->getRelation($relation); + + foreach ($attrs as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $this->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->setAttr($key, $relation ? $relation->getAttr($attr) : null); + } + + return $this; + } + + /** + * 关联数据写入 + * @access public + * @param array|string $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + + $this->together = $relation; + + $this->checkAutoRelationWrite(); + + return $this; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public static function has($relation, $operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $relation = (new static())->$relation(); + + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + + return $relation->has($operator, $count, $id, $joinType); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public static function hasWhere($relation, $where = [], $fields = '*') + { + return (new static())->$relation()->hasWhere($where, $fields); + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param string|array $relations 关联名 + * @param array $withRelationAttr 关联获取器 + * @return $this + */ + public function relationQuery($relations, $withRelationAttr = []) + { + if (is_string($relations)) { + $relations = explode(',', $relations); + } + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $method = Loader::parseName($relation, 1, false); + $relationName = Loader::parseName($relation); + + $relationResult = $this->$method(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]); + } + + $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure); + } + + return $this; + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation, $withRelationAttr = [], $join = false) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $relation = Loader::parseName($relation, 1, false); + $relationName = Loader::parseName($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]); + } + + $relationResult->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $join); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @return Model + */ + public function eagerlyResult(&$result, $relation, $withRelationAttr = [], $join = false) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $relation = Loader::parseName($relation, 1, false); + $relationName = Loader::parseName($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]); + } + + $relationResult->eagerlyResult($result, $relation, $subRelation, $closure, $join); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param array $relations 关联名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @return void + */ + public function relationCount(&$result, $relations, $aggregate = 'sum', $field = '*') + { + foreach ($relations as $key => $relation) { + $closure = $name = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + + $relation = Loader::parseName($relation, 1, false); + + $count = $this->$relation()->relationCount($result, $closure, $aggregate, $field, $name); + + if (empty($name)) { + $name = Loader::parseName($relation) . '_' . $aggregate; + } + + $result->setAttr($name, $count); + } + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasOne + */ + public function hasOne($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasOne($this, $model, $foreignKey, $localKey); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @return BelongsTo + */ + public function belongsTo($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName()); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + return new BelongsTo($this, $model, $foreignKey, $localKey, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasMany + */ + public function hasMany($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @return HasManyThrough + */ + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Loader::parseName(basename(str_replace('\\', '/', $model))); + $table = $table ?: Loader::parseName($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + $type = $type ?: get_class($this); + + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + + $type = $type ?: get_class($this); + + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (false === strpos($model, '\\')) { + $path = explode('\\', static::class); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 获取模型的默认外键名 + * @access protected + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + + return Loader::parseName($name) . '_id'; + } + + /** + * 检查属性是否为关联属性 如果是则返回关联方法名 + * @access protected + * @param string $attr 关联属性名 + * @return string|false + */ + protected function isRelationAttr($attr) + { + $relation = Loader::parseName($attr, 1, false); + + if (method_exists($this, $relation) && !method_exists('think\Model', $relation)) { + return $relation; + } + + return false; + } + + /** + * 智能获取关联模型数据 + * @access protected + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) { + $value = $this->parent; + } else { + // 获取关联数据 + $value = $modelRelation->getRelation(); + } + + return $value; + } + + /** + * 关联数据自动写入检查 + * @access protected + * @return void + */ + protected function checkAutoRelationWrite() + { + foreach ($this->together as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $this->relationWrite[$key] = []; + // 绑定关联属性 + foreach ((array) $name as $val) { + if (isset($this->data[$val])) { + $this->relationWrite[$key][$val] = $this->data[$val]; + } + } + } else { + // 直接传入关联数据 + $this->relationWrite[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $this->relationWrite[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $this->relationWrite[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + /** + * 自动关联数据更新(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationUpdate() + { + foreach ($this->relationWrite as $name => $val) { + if ($val instanceof Model) { + $val->isUpdate()->save(); + } else { + $model = $this->getRelation($name); + if ($model instanceof Model) { + $model->isUpdate()->save($val); + } + } + } + } + + /** + * 自动关联数据写入(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationInsert() + { + foreach ($this->relationWrite as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + + /** + * 自动关联数据删除(支持一对一及一对多关联) + * @access protected + * @return void + */ + protected function autoRelationDelete() + { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/concern/SoftDelete.php b/Server/application/common/Server/thinkphp/library/think/model/concern/SoftDelete.php new file mode 100644 index 00000000..ec866ac0 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/concern/SoftDelete.php @@ -0,0 +1,246 @@ +getDeleteTimeField(); + + if ($field && !empty($this->getOrigin($field))) { + return true; + } + + return false; + } + + /** + * 查询软删除数据 + * @access public + * @return Query + */ + public static function withTrashed() + { + $model = new static(); + + return $model->withTrashedData(true)->db(false); + } + + /** + * 是否包含软删除数据 + * @access protected + * @param bool $withTrashed 是否包含软删除数据 + * @return $this + */ + protected function withTrashedData($withTrashed) + { + $this->withTrashed = $withTrashed; + return $this; + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed() + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model + ->db(false) + ->useSoftDelete($field, $model->getWithTrashedExp()); + } + + return $model->db(false); + } + + /** + * 获取软删除数据的查询条件 + * @access protected + * @return array + */ + protected function getWithTrashedExp() + { + return is_null($this->defaultSoftDelete) ? + ['notnull', ''] : ['<>', $this->defaultSoftDelete]; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete($force = false) + { + if (!$this->isExists() || false === $this->trigger('before_delete', $this)) { + return false; + } + + $force = $force ?: $this->isForce(); + $name = $this->getDeleteTimeField(); + + if ($name && !$force) { + // 软删除 + $this->data($name, $this->autoWriteTimestamp($name)); + + $result = $this->isUpdate()->withEvent(false)->save(); + + $this->withEvent(true); + } else { + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->db(false) + ->where($where) + ->removeOption('soft_delete') + ->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $this->trigger('after_delete', $this); + + $this->exists(false); + + return true; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, $force = false) + { + // 传入空不执行删除,但是0可以删除 + if (empty($data) && 0 !== $data) { + return false; + } + // 包含软删除数据 + $query = (new static())->db(false); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } elseif (is_null($data)) { + return false; + } + + $resultSet = $query->select($data); + + if ($resultSet) { + foreach ($resultSet as $data) { + $data->force($force)->delete(); + } + } + + return true; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return bool + */ + public function restore($where = []) + { + $name = $this->getDeleteTimeField(); + + if ($name) { + if (false === $this->trigger('before_restore')) { + return false; + } + + if (empty($where)) { + $pk = $this->getPk(); + + $where[] = [$pk, '=', $this->getData($pk)]; + } + + // 恢复删除 + $this->db(false) + ->where($where) + ->useSoftDelete($name, $this->getWithTrashedExp()) + ->update([$name => $this->defaultSoftDelete]); + + $this->trigger('after_restore'); + + return true; + } + + return false; + } + + /** + * 获取软删除字段 + * @access protected + * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 + * @return string|false + */ + protected function getDeleteTimeField($read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + + if (false === $field) { + return false; + } + + if (false === strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } + + /** + * 查询的时候默认排除软删除数据 + * @access protected + * @param Query $query + * @return void + */ + protected function withNoTrashed($query) + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete]; + $query->useSoftDelete($field, $condition); + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/concern/TimeStamp.php b/Server/application/common/Server/thinkphp/library/think/model/concern/TimeStamp.php new file mode 100644 index 00000000..99a31fa7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/concern/TimeStamp.php @@ -0,0 +1,92 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use DateTime; + +/** + * 自动时间戳 + */ +trait TimeStamp +{ + /** + * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + * @var bool|string + */ + protected $autoWriteTimestamp; + + /** + * 创建时间字段 false表示关闭 + * @var false|string + */ + protected $createTime = 'create_time'; + + /** + * 更新时间字段 false表示关闭 + * @var false|string + */ + protected $updateTime = 'update_time'; + + /** + * 时间字段显示格式 + * @var string + */ + protected $dateFormat; + + /** + * 时间日期字段格式化处理 + * @access protected + * @param mixed $format 日期格式 + * @param mixed $time 时间日期表达式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($format, $time = 'now', $timestamp = false) + { + if (empty($time)) { + return; + } + + if (false === $format) { + return $time; + } elseif (false !== strpos($format, '\\')) { + return new $format($time); + } + + if ($timestamp) { + $dateTime = new DateTime(); + $dateTime->setTimestamp($time); + } else { + $dateTime = new DateTime($time); + } + + return $dateTime->format($format); + } + + /** + * 检查时间字段写入 + * @access protected + * @return void + */ + protected function checkTimeStampWrite() + { + // 自动写入创建时间和更新时间 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/BelongsTo.php b/Server/application/common/Server/thinkphp/library/think/model/relation/BelongsTo.php new file mode 100644 index 00000000..056c7d76 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/BelongsTo.php @@ -0,0 +1,323 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Loader; +use think\Model; + +class BelongsTo extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = 'INNER'; + $this->query = (new $model)->db(); + $this->relation = $relation; + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure instanceof Closure) { + $closure($this->query); + } + + $foreignKey = $this->foreignKey; + + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $foreignKey = $this->foreignKey; + + if (!isset($result->$foreignKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->localKey, '=', $result->$foreignKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation]) + ->field($relation . '.' . $localKey) + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, 'in', $range], + ], $localKey, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $this->parent->setAttr($this->foreignKey, $model->getKey()); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $this->parent->setAttr($this->foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/BelongsToMany.php b/Server/application/common/Server/thinkphp/library/think/model/relation/BelongsToMany.php new file mode 100644 index 00000000..6105e233 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,712 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +class BelongsToMany extends Relation +{ + // 中间表表名 + protected $middle; + // 中间表模型名称 + protected $pivotName; + // 中间表数据名称 + protected $pivotDataName = 'pivot'; + // 中间表模型对象 + protected $pivot; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + } + + /** + * 设置中间表模型 + * @access public + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function pivotDataName($name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + + /** + * 实例化中间表模型 + * @access public + * @param array $data + * @param bool $isUpdate + * @return Pivot + * @throws Exception + */ + protected function newPivot($data = [], $isUpdate = false) + { + $class = $this->pivotName ?: '\\think\\model\\Pivot'; + $pivot = new $class($data, $this->parent, $this->middle); + + if ($pivot instanceof Pivot) { + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; + } + + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + + /** + * 合成中间表模型 + * @access protected + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + } + } + + /** + * 创建关联查询Query对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + $pk = $this->parent->getPk(); + + $condition[] = ['pivot.' . $localKey, '=', $this->parent->$pk]; + + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure instanceof Closure) { + $closure($this->query); + } + + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载select方法 + * @access public + * @param mixed $data + * @return Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载paginate方法 + * @access public + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载find方法 + * @access public + * @param mixed $data + * @return Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + if ($result) { + $this->hydratePivot([$result]); + } + + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @access public + * @param string $field + * @param string $op + * @param mixed $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $this->query->where('pivot.' . $field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $localKey, 'in', $range], + ], $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $relation, $subRelation, $closure); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return array + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + [ + 'pivot.' . $this->localKey, 'exp', $this->query->raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->$aggregate($field); + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + + $data[$pivot[$this->localKey]][] = $set; + } + + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query + ->field($fields) + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = []; + + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + + $result[] = $this->attach($data, $pivotData); + } + + return empty($result) ? false : $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot, true); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + * @throws Exception + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[] = [$this->localKey, '=', $this->parent->$pk]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $pk = $this->parent->getPk(); + + $current = $this->pivot + ->where($this->localKey, $this->parent->$pk) + ->column($this->foreignKey); + + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) { + $changes['updated'][] = $id; + } + } + + return $changes; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + + $this->query + ->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk()) + ->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/HasMany.php b/Server/application/common/Server/thinkphp/library/think/model/relation/HasMany.php new file mode 100644 index 00000000..e4df5c4b --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/HasMany.php @@ -0,0 +1,360 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select(); + + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $where = [ + [$this->foreignKey, 'in', $range], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $pk = $result->$localKey; + $where = [ + [$this->foreignKey, '=', $pk], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query->alias($aggregate . '_table') + ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure + * @return array + */ + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + + // 预载入关联查询 支持嵌套预载入 + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->query->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, $replace = true) + { + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array|\think\Collection $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll($dataSet, $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->group($model . '.' . $this->localKey) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/HasManyThrough.php b/Server/application/common/Server/thinkphp/library/think/model/relation/HasManyThrough.php new file mode 100644 index 00000000..be0b0cd9 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,363 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 中间主键 + * @var string + */ + protected $throughPk; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->through = (new $through)->db(); + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->throughPk = $this->through->getPk(); + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure instanceof Closure) { + $closure($this->query); + } + + $this->baseQuery(); + + return $this->query->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $model = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $relation = (new $this->model)->db(); + $relationTable = $relation->getTable(); + $softDelete = $this->query->getOptions('soft_delete'); + + if ('*' != $id) { + $id = $relationTable . '.' . $relation->getPk(); + } + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $relationTable) { + $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relationTable . '.' . $this->throughKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $model = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = (new $this->model)->db()->getTable(); + + if (is_array($where)) { + $this->getQueryWhere($where, $modelTable); + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $modelTable) { + $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($modelTable . '.' . $this->throughKey) + ->where($where) + ->field($fields); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param mixed $subRelation 子关联名 + * @param Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, $relation, $subRelation = '', $closure = null) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + // 设置关联属性 + $result->setRelation($attr, $this->resultSetBuild($data[$pk])); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param mixed $subRelation 子关联名 + * @param Closure $closure 闭包 + * @return void + */ + public function eagerlyResult($result, $relation, $subRelation = '', $closure = null) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $pk = $result->$localKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $pk], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param mixed $subRelation 子关联 + * @param Closure $closure + * @return array + */ + protected function eagerlyWhere(array $where, $key, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + $throughList = $this->through->where($where)->select(); + $keys = $throughList->column($this->throughPk, $this->throughPk); + + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->query->where($this->throughKey, 'in', $keys)->select(); + + // 组装模型数据 + $data = []; + $keys = $throughList->column($this->foreignKey, $this->throughPk); + + foreach ($list as $set) { + $data[$keys[$set->{$this->throughKey}]][] = $set; + } + + return $data; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery($closure = null, $aggregate = 'count', $field = '*', &$name = null) + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + + $this->baseQuery = true; + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/HasOne.php b/Server/application/common/Server/thinkphp/library/think/model/relation/HasOne.php new file mode 100644 index 00000000..fe09443c --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/HasOne.php @@ -0,0 +1,294 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = 'INNER'; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $localKey = $this->localKey; + + if ($closure instanceof Closure) { + $closure($this->query); + } + + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, 'in', $range], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/MorphMany.php b/Server/application/common/Server/thinkphp/library/think/model/relation/MorphMany.php new file mode 100644 index 00000000..d2af66e9 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/MorphMany.php @@ -0,0 +1,342 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure instanceof Closure) { + $closure($this->query); + } + + $this->baseQuery(); + + $list = $this->query->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $key = $result->$pk; + $where = [ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + + if (!isset($data[$key])) { + $data[$key] = []; + } + + foreach ($data[$key] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$key])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 多态一对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + $this->query->removeOption('where'); + + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 + * @return Model|false + */ + public function save($data) + { + $model = $this->make(); + + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return array|false + */ + public function saveAll(array $dataSet) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data); + } + + return empty($result) ? false : $result; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/MorphOne.php b/Server/application/common/Server/thinkphp/library/think/model/relation/MorphOne.php new file mode 100644 index 00000000..6bc205c5 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/MorphOne.php @@ -0,0 +1,257 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure instanceof Closure) { + $closure($this->query); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ], $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$morphKey] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 + * @return Model|false + */ + public function save($data) + { + $model = $this->make(); + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/MorphTo.php b/Server/application/common/Server/thinkphp/library/think/model/relation/MorphTo.php new file mode 100644 index 00000000..0786c2fe --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/MorphTo.php @@ -0,0 +1,316 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + // 关联名 + protected $relation; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + + return (new $model); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + + // 主键数据 + $pk = $this->parent->$morphKey; + + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = (new $model)->db(); + $pk = $obj->getPk(); + // 预载入关联查询 支持嵌套预载入 + if ($closure instanceof \Closure) { + $closure($obj); + + if ($field = $obj->getOptions('with_field')) { + $obj->field($field)->removeOption('with_field'); + } + } + $list = $obj->all($val, $subRelation); + $data = []; + + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + {} + + /** + * 多态MorphTo 关联模型预查询 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + + if ($data) { + $data->setParent(clone $result); + $data->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate($model, $type = '') + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/model/relation/OneToOne.php b/Server/application/common/Server/thinkphp/library/think/model/relation/OneToOne.php new file mode 100644 index 00000000..5e22b800 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/model/relation/OneToOne.php @@ -0,0 +1,337 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + // 关联名 + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param mixed $field 关联字段 + * @param string $joinType JOIN方式 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $field, $joinType, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + + if ($query->getOptions('field')) { + $masterField = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $masterField = true; + } + + $query->field($masterField, false, $table, $name); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $joinType = $joinType ?: $this->joinType; + + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey; + } else { + $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey; + } + + if ($closure instanceof Closure) { + // 执行闭包查询 + $closure($query); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } + + $query->join([$joinTable => $joinAlias], $joinOn, $joinType) + ->field($field, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $join = false) + { + if ($join || 0 == $this->eagerlyType) { + // 模型JOIN关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } else { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $join = false) + { + if (0 == $this->eagerlyType || $join) { + // 模型JOIN关联组装 + $this->match($this->model, $relation, $result); + } else { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return $model->save($data) ? $model : false; + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $array = array_unique($list[$relation]); + + if (count($array) == 1 && null === current($array)) { + $relationModel = null; + } else { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $result 关联模型对象 + * @param Model $model 父模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result) + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $result->setAttr($key, $model ? $model->getAttr($attr) : null); + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure + * @return array + */ + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure instanceof Closure) { + $closure($this->query); + + if ($field = $this->query->getOptions('with_field')) { + $this->query->field($field)->removeOption('with_field'); + } + } + + $list = $this->query->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$key] = $set; + } + + return $data; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/paginator/driver/Bootstrap.php b/Server/application/common/Server/thinkphp/library/think/paginator/driver/Bootstrap.php new file mode 100644 index 00000000..ab5315c0 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -0,0 +1,206 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton($text = "«") + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton($text = '»') + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks() + { + if ($this->simple) { + return ''; + } + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null, + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
    %s %s
', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
    %s %s %s
', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getPageLinkWrapper($url, $page) + { + if ($this->currentPage() == $page) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/Builder.php b/Server/application/common/Server/thinkphp/library/think/process/Builder.php new file mode 100644 index 00000000..da561639 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/Builder.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +use think\Process; + +class Builder +{ + private $arguments; + private $cwd; + private $env = null; + private $input; + private $timeout = 60; + private $options = []; + private $inheritEnv = true; + private $prefix = []; + private $outputDisabled = false; + + /** + * 构造方法 + * @param string[] $arguments 参数 + */ + public function __construct(array $arguments = []) + { + $this->arguments = $arguments; + } + + /** + * 创建一个实例 + * @param string[] $arguments 参数 + * @return self + */ + public static function create(array $arguments = []) + { + return new static($arguments); + } + + /** + * 添加一个参数 + * @param string $argument 参数 + * @return self + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * 添加一个前缀 + * @param string|array $prefix + * @return self + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : [$prefix]; + + return $this; + } + + /** + * 设置参数 + * @param string[] $arguments + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * 设置工作目录 + * @param null|string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * 是否初始化环境变量 + * @param bool $inheritEnv + * @return self + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * 设置环境变量 + * @param string $name + * @param null|string $value + * @return self + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * 添加环境变量 + * @param array $variables + * @return self + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) + { + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 设置超时时间 + * @param float|null $timeout + * @return self + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * 设置proc_open选项 + * @param string $name + * @param string $value + * @return self + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * 禁止输出 + * @return self + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启输出 + * @return self + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * 创建一个Process实例 + * @return Process + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new \LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/Utils.php b/Server/application/common/Server/thinkphp/library/think/process/Utils.php new file mode 100644 index 00000000..f94c6488 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/Utils.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +class Utils +{ + + /** + * 转义字符串 + * @param string $argument + * @return string + */ + public static function escapeArgument($argument) + { + + if ('' === $argument) { + return escapeshellarg($argument); + } + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"' . $escapedArgument . '"'; + } + return $escapedArgument; + } + + /** + * 验证并进行规范化Process输入。 + * @param string $caller + * @param mixed $input + * @return string + * @throws \InvalidArgumentException + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/exception/Faild.php b/Server/application/common/Server/thinkphp/library/think/process/exception/Faild.php new file mode 100644 index 00000000..38647bc1 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/exception/Faild.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Faild extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/exception/Failed.php b/Server/application/common/Server/thinkphp/library/think/process/exception/Failed.php new file mode 100644 index 00000000..52950823 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/exception/Timeout.php b/Server/application/common/Server/thinkphp/library/think/process/exception/Timeout.php new file mode 100644 index 00000000..d5f1162f --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/exception/Timeout.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Timeout extends \RuntimeException +{ + + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/pipes/Pipes.php b/Server/application/common/Server/thinkphp/library/think/process/pipes/Pipes.php new file mode 100644 index 00000000..82396b8f --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/pipes/Pipes.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +abstract class Pipes +{ + + /** @var array */ + public $pipes = []; + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + const CHUNK_SIZE = 16384; + + /** + * 返回用于 proc_open 描述符的数组 + * @return array + */ + abstract public function getDescriptors(); + + /** + * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 + * @return string[] + */ + abstract public function getFiles(); + + /** + * 文件句柄和管道中读取数据。 + * @param bool $blocking 是否使用阻塞调用 + * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 + * @return string[] + */ + abstract public function readAndWrite($blocking, $close = false); + + /** + * 返回当前状态如果有打开的文件句柄或管道。 + * @return bool + */ + abstract public function areOpen(); + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * 检查系统调用已被中断 + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/pipes/Unix.php b/Server/application/common/Server/thinkphp/library/think/process/pipes/Unix.php new file mode 100644 index 00000000..fd99a5d6 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/pipes/Unix.php @@ -0,0 +1,196 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Unix extends Pipes +{ + + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + + if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return []; + } + + $this->unblock(); + + $read = []; + + if (null !== $this->input) { + $r = array_merge($this->pipes, ['input' => $this->input]); + } else { + $r = $this->pipes; + } + + unset($r[0]); + + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ('input' === $type) { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ('input' === $type) { + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * 创建一个新的 UnixPipes 实例 + * @param Process $process + * @param string|resource $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/process/pipes/Windows.php b/Server/application/common/Server/thinkphp/library/think/process/pipes/Windows.php new file mode 100644 index 00000000..1b8b0d4f --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/process/pipes/Windows.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Windows extends Pipes +{ + + /** @var array */ + private $files = []; + /** @var array */ + private $fileHandles = []; + /** @var array */ + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + + $this->files = [ + Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), + ]; + foreach ($this->files as $offset => $file) { + $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); + if (false === $this->fileHandles[$offset]) { + throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = []; + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = []; + } + + /** + * 创建一个新的 WindowsPipes 实例。 + * @param Process $process + * @param $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * 删除临时文件 + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = []; + } + + /** + * 写入到 stdin 输入 + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? ['input' => $this->input] : null; + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return; + } + + if (0 === $n) { + return; + } + + if (null !== $r && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/response/Download.php b/Server/application/common/Server/thinkphp/library/think/response/Download.php new file mode 100644 index 00000000..5595f9ab --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/response/Download.php @@ -0,0 +1,148 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Exception; +use think\Response; + +class Download extends Response +{ + protected $expire = 360; + protected $name; + protected $mimeType; + protected $isContent = false; + protected $openinBrowser = false; + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + if (!$this->isContent && !is_file($data)) { + throw new Exception('file not exists:' . $data); + } + + ob_end_clean(); + + if (!empty($this->name)) { + $name = $this->name; + } else { + $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : ''; + } + + if ($this->isContent) { + $mimeType = $this->mimeType; + $size = strlen($data); + } else { + $mimeType = $this->getMimeType($data); + $size = filesize($data); + } + + $this->header['Pragma'] = 'public'; + $this->header['Content-Type'] = $mimeType ?: 'application/octet-stream'; + $this->header['Cache-control'] = 'max-age=' . $this->expire; + $this->header['Content-Disposition'] = $this->openinBrowser ? 'inline' : 'attachment; filename="' . $name . '"'; + $this->header['Content-Length'] = $size; + $this->header['Content-Transfer-Encoding'] = 'binary'; + $this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT'; + + $this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT'); + + $data = $this->isContent ? $data : file_get_contents($data); + return $data; + } + + /** + * 设置是否为内容 必须配合mimeType方法使用 + * @access public + * @param bool $content + * @return $this + */ + public function isContent($content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 设置有效期 + * @access public + * @param integer $expire 有效期 + * @return $this + */ + public function expire($expire) + { + $this->expire = $expire; + return $this; + } + + /** + * 设置文件类型 + * @access public + * @param string $filename 文件名 + * @return $this + */ + public function mimeType($mimeType) + { + $this->mimeType = $mimeType; + return $this; + } + + /** + * 获取文件类型信息 + * @access public + * @param string $filename 文件名 + * @return string + */ + protected function getMimeType($filename) + { + if (!empty($this->mimeType)) { + return $this->mimeType; + } + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $filename); + } + + /** + * 设置下载文件的显示名称 + * @access public + * @param string $filename 文件名 + * @param bool $extension 后缀自动识别 + * @return $this + */ + public function name($filename, $extension = true) + { + $this->name = $filename; + + if ($extension && false === strpos($filename, '.')) { + $this->name .= '.' . pathinfo($this->data, PATHINFO_EXTENSION); + } + + return $this; + } + + /** + * 设置是否在浏览器中显示文件 + * @access public + * @param bool $openinBrowser 是否在浏览器中显示文件 + * @return $this + */ + public function openinBrowser($openinBrowser) { + $this->openinBrowser = $openinBrowser; + return $this; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/response/Json.php b/Server/application/common/Server/thinkphp/library/think/response/Json.php new file mode 100644 index 00000000..aa5bbd6f --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/response/Json.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/response/Jsonp.php b/Server/application/common/Server/thinkphp/library/think/response/Jsonp.php new file mode 100644 index 00000000..f69e88e1 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/response/Jsonp.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Jsonp extends Response +{ + // 输出参数 + protected $options = [ + 'var_jsonp_handler' => 'callback', + 'default_jsonp_handler' => 'jsonpReturn', + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/javascript'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = $this->app['request']->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/response/Jump.php b/Server/application/common/Server/thinkphp/library/think/response/Jump.php new file mode 100644 index 00000000..258448ca --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/response/Jump.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Jump extends Response +{ + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + $data = $this->app['view']->fetch($this->options['jump_template'], $data); + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/response/Redirect.php b/Server/application/common/Server/thinkphp/library/think/response/Redirect.php new file mode 100644 index 00000000..6b4f118a --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/response/Redirect.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Redirect extends Response +{ + + protected $options = []; + + // URL参数 + protected $params = []; + + public function __construct($data = '', $code = 302, array $header = [], array $options = []) + { + parent::__construct($data, $code, $header, $options); + + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + $this->header['Location'] = $this->getTargetUrl(); + + return; + } + + /** + * 重定向传值(通过Session) + * @access protected + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 + * @return $this + */ + public function with($name, $value = null) + { + $session = $this->app['session']; + + if (is_array($name)) { + foreach ($name as $key => $val) { + $session->flash($key, $val); + } + } else { + $session->flash($name, $value); + } + + return $this; + } + + /** + * 获取跳转地址 + * @access public + * @return string + */ + public function getTargetUrl() + { + if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { + return $this->data; + } else { + return $this->app['url']->build($this->data, $this->params); + } + } + + public function params($params = []) + { + $this->params = $params; + + return $this; + } + + /** + * 记住当前url后跳转 + * @access public + * @param string $url 指定记住的url + * @return $this + */ + public function remember($url = null) + { + $this->app['session']->set('redirect_url', $url ?: $this->app['request']->url()); + + return $this; + } + + /** + * 跳转到上次记住的url + * @access public + * @param string $url 闪存数据不存在时的跳转地址 + * @return $this + */ + public function restore($url = null) + { + $session = $this->app['session']; + + if ($session->has('redirect_url')) { + $this->data = $session->get('redirect_url'); + $session->delete('redirect_url'); + } elseif ($url) { + $this->data = $url; + } + + return $this; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/response/View.php b/Server/application/common/Server/thinkphp/library/think/response/View.php new file mode 100644 index 00000000..3d54c735 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/response/View.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class View extends Response +{ + // 输出参数 + protected $options = []; + protected $vars = []; + protected $config = []; + protected $filter; + protected $contentType = 'text/html'; + + /** + * 是否内容渲染 + * @var bool + */ + protected $isContent = false; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // 渲染模板输出 + return $this->app['view'] + ->filter($this->filter) + ->fetch($data, $this->vars, $this->config, $this->isContent); + } + + /** + * 设置是否为内容渲染 + * @access public + * @param bool $content + * @return $this + */ + public function isContent($content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars($name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return isset($this->vars[$name]) ? $this->vars[$name] : null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + } else { + $this->vars[$name] = $value; + } + + return $this; + } + + public function config($config) + { + $this->config = $config; + return $this; + } + + /** + * 视图内容过滤 + * @access public + * @param callable $filter + * @return $this + */ + public function filter($filter) + { + $this->filter = $filter; + return $this; + } + + /** + * 检查模板是否存在 + * @access private + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->app['view']->exists($name); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/response/Xml.php b/Server/application/common/Server/thinkphp/library/think/response/Xml.php new file mode 100644 index 00000000..9c1681a4 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/response/Xml.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Collection; +use think\Model; +use think\Response; + +class Xml extends Response +{ + // 输出参数 + protected $options = [ + // 根节点名 + 'root_node' => 'think', + // 根节点属性 + 'root_attr' => '', + //数字索引的子节点名 + 'item_node' => 'item', + // 数字索引子节点key转换的属性名 + 'item_key' => 'id', + // 数据编码 + 'encoding' => 'utf-8', + ]; + + protected $contentType = 'text/xml'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + if (is_string($data)) { + if (0 !== strpos($data, 'options['encoding']; + $xml = ""; + $data = $xml . $data; + } + return $data; + } + + // XML数据转换 + return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); + } + + /** + * XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) + { + if (is_array($attr)) { + $array = []; + foreach ($attr as $key => $value) { + $array[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $array); + } + + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + + return $xml; + } + + /** + * 数据XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, $item, $id) + { + $xml = $attr = ''; + + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; + $xml .= ""; + } + + return $xml; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/AliasRule.php b/Server/application/common/Server/thinkphp/library/think/route/AliasRule.php new file mode 100644 index 00000000..393cb310 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/AliasRule.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Route; + +class AliasRule extends Domain +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由别名 + * @param string $route 路由绑定 + * @param array $option 路由参数 + */ + public function __construct(Route $router, RuleGroup $parent, $name, $route, $option = []) + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->option = $option; + } + + /** + * 检测路由别名 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + list($action, $bind) = array_pad(explode('|', $url, 2), 2, ''); + + if (isset($this->option['allow']) && !in_array($action, $this->option['allow'])) { + // 允许操作 + return false; + } elseif (isset($this->option['except']) && in_array($action, $this->option['except'])) { + // 排除操作 + return false; + } + + if (isset($this->option['method'][$action])) { + $this->option['method'] = $this->option['method'][$action]; + } + + // 匹配后执行的行为 + $this->afterMatchGroup($request); + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + } + + if (isset($this->option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $bind = preg_replace('/\.(' . $request->ext() . ')$/i', '', $bind); + } + + $this->parseBindAppendParam($this->route); + + if (0 === strpos($this->route, '\\')) { + // 路由到类 + return $this->bindToClass($request, $bind, substr($this->route, 1)); + } elseif (0 === strpos($this->route, '@')) { + // 路由到控制器类 + return $this->bindToController($request, $bind, substr($this->route, 1)); + } else { + // 路由到模块/控制器 + return $this->bindToModule($request, $bind, $this->route); + } + } + + /** + * 设置允许的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ + public function allow($action = []) + { + return $this->option('allow', $action); + } + + /** + * 设置排除的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ + public function except($action = []) + { + return $this->option('except', $action); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/Dispatch.php b/Server/application/common/Server/thinkphp/library/think/route/Dispatch.php new file mode 100644 index 00000000..7323c98d --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/Dispatch.php @@ -0,0 +1,366 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\App; +use think\Container; +use think\exception\ValidateException; +use think\Request; +use think\Response; + +abstract class Dispatch +{ + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 路由规则 + * @var Rule + */ + protected $rule; + + /** + * 调度信息 + * @var mixed + */ + protected $dispatch; + + /** + * 调度参数 + * @var array + */ + protected $param; + + /** + * 状态码 + * @var string + */ + protected $code; + + /** + * 是否进行大小写转换 + * @var bool + */ + protected $convert; + + public function __construct(Request $request, Rule $rule, $dispatch, $param = [], $code = null) + { + $this->request = $request; + $this->rule = $rule; + $this->app = Container::get('app'); + $this->dispatch = $dispatch; + $this->param = $param; + $this->code = $code; + + if (isset($param['convert'])) { + $this->convert = $param['convert']; + } + } + + public function init() + { + // 执行路由后置操作 + if ($this->rule->doAfter()) { + // 设置请求的路由信息 + + // 设置当前请求的参数 + $this->request->setRouteVars($this->rule->getVars()); + $this->request->routeInfo([ + 'rule' => $this->rule->getRule(), + 'route' => $this->rule->getRoute(), + 'option' => $this->rule->getOption(), + 'var' => $this->rule->getVars(), + ]); + + $this->doRouteAfter(); + } + + return $this; + } + + /** + * 检查路由后置操作 + * @access protected + * @return void + */ + protected function doRouteAfter() + { + // 记录匹配的路由信息 + $option = $this->rule->getOption(); + $matches = $this->rule->getVars(); + + // 添加中间件 + if (!empty($option['middleware'])) { + $this->app['middleware']->import($option['middleware']); + } + + // 绑定模型数据 + if (!empty($option['model'])) { + $this->createBindModel($option['model'], $matches); + } + + // 指定Header数据 + if (!empty($option['header'])) { + $header = $option['header']; + $this->app['hook']->add('response_send', function ($response) use ($header) { + $response->header($header); + }); + } + + // 指定Response响应数据 + if (!empty($option['response'])) { + foreach ($option['response'] as $response) { + $this->app['hook']->add('response_send', $response); + } + } + + // 开启请求缓存 + if (isset($option['cache']) && $this->request->isGet()) { + $this->parseRequestCache($option['cache']); + } + + if (!empty($option['append'])) { + $this->request->setRouteVars($option['append']); + } + } + + /** + * 执行路由调度 + * @access public + * @return mixed + */ + public function run() + { + $option = $this->rule->getOption(); + + // 检测路由after行为 + if (!empty($option['after'])) { + $dispatch = $this->checkAfter($option['after']); + + if ($dispatch instanceof Response) { + return $dispatch; + } + } + + // 数据自动验证 + if (isset($option['validate'])) { + $this->autoValidate($option['validate']); + } + + $data = $this->exec(); + + return $this->autoResponse($data); + } + + protected function autoResponse($data) + { + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $this->request->isAjax(); + $type = $isAjax ? $this->rule->getConfig('default_ajax_return') : $this->rule->getConfig('default_return_type'); + + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + $content = false === $data ? '' : $data; + $status = '' === $content && $this->request->isJson() ? 204 : 200; + + $response = Response::create($content, '', $status); + } + + return $response; + } + + /** + * 检查路由后置行为 + * @access protected + * @param mixed $after 后置行为 + * @return mixed + */ + protected function checkAfter($after) + { + $this->app['log']->notice('路由后置行为建议使用中间件替代!'); + + $hook = $this->app['hook']; + + $result = null; + + foreach ((array) $after as $behavior) { + $result = $hook->exec($behavior); + + if (!is_null($result)) { + break; + } + } + + // 路由规则重定向 + if ($result instanceof Response) { + return $result; + } + + return false; + } + + /** + * 验证数据 + * @access protected + * @param array $option + * @return void + * @throws ValidateException + */ + protected function autoValidate($option) + { + list($validate, $scene, $message, $batch) = $option; + + if (is_array($validate)) { + // 指定验证规则 + $v = $this->app->validate(); + $v->rule($validate); + } else { + // 调用验证器 + $v = $this->app->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } + } + + if (!empty($message)) { + $v->message($message); + } + + // 批量验证 + if ($batch) { + $v->batch(true); + } + + if (!$v->check($this->request->param())) { + throw new ValidateException($v->getError()); + } + } + + /** + * 处理路由请求缓存 + * @access protected + * @param Request $request 请求对象 + * @param string|array $cache 路由缓存 + * @return void + */ + protected function parseRequestCache($cache) + { + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $this->request->url()); + $expire = $cache; + $tag = null; + } + + $cache = $this->request->cache($key, $expire, $tag); + $this->app->setResponseCache($cache); + } + + /** + * 路由绑定模型实例 + * @access protected + * @param array|\Clousre $bindModel 绑定模型 + * @param array $matches 路由变量 + * @return void + */ + protected function createBindModel($bindModel, $matches) + { + foreach ($bindModel as $key => $val) { + if ($val instanceof \Closure) { + $result = $this->app->invokeFunction($val, $matches); + } else { + $fields = explode('&', $key); + + if (is_array($val)) { + list($model, $exception) = $val; + } else { + $model = $val; + $exception = true; + } + + $where = []; + $match = true; + + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[] = [$field, '=', $matches[$field]]; + } + } + + if ($match) { + $query = strpos($model, '\\') ? $model::where($where) : $this->app->model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + + if (!empty($result)) { + // 注入容器 + $this->app->instance(get_class($result), $result); + } + } + } + + public function convert($convert) + { + $this->convert = $convert; + + return $this; + } + + public function getDispatch() + { + return $this->dispatch; + } + + public function getParam() + { + return $this->param; + } + + abstract public function exec(); + + public function __sleep() + { + return ['rule', 'dispatch', 'convert', 'param', 'code', 'controller', 'actionName']; + } + + public function __wakeup() + { + $this->app = Container::get('app'); + $this->request = $this->app['request']; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request'], $data['rule']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/Domain.php b/Server/application/common/Server/thinkphp/library/think/route/Domain.php new file mode 100644 index 00000000..923d9b42 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/Domain.php @@ -0,0 +1,237 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Loader; +use think\Request; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; +use think\route\dispatch\Module as ModuleDispatch; + +class Domain extends RuleGroup +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param string $name 路由域名 + * @param mixed $rule 域名路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, $name = '', $rule = null, $option = [], $pattern = []) + { + $this->router = $router; + $this->domain = $name; + $this->option = $option; + $this->rule = $rule; + $this->pattern = $pattern; + } + + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + // 检测别名路由 + $result = $this->checkRouteAlias($request, $url); + + if (false !== $result) { + return $result; + } + + // 检测URL绑定 + $result = $this->checkUrlBind($request, $url); + + if (!empty($this->option['append'])) { + $request->setRouteVars($this->option['append']); + unset($this->option['append']); + } + + if (false !== $result) { + return $result; + } + + // 添加域名中间件 + if (!empty($this->option['middleware'])) { + Container::get('middleware')->import($this->option['middleware']); + unset($this->option['middleware']); + } + + return parent::check($request, $url, $completeMatch); + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @return $this + */ + public function bind($bind) + { + $this->router->bind($bind, $this->domain); + return $this; + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @return Dispatch|false + */ + private function checkRouteAlias($request, $url) + { + $alias = strpos($url, '|') ? strstr($url, '|', true) : $url; + + $item = $this->router->getAlias($alias); + + return $item ? $item->check($request, $url) : false; + } + + /** + * 检测URL绑定 + * @access private + * @param Request $request + * @param string $url URL地址 + * @return Dispatch|false + */ + private function checkUrlBind($request, $url) + { + $bind = $this->router->getBind($this->domain); + + if (!empty($bind)) { + $this->parseBindAppendParam($bind); + + // 记录绑定信息 + Container::get('app')->log('[ BIND ] ' . var_export($bind, true)); + + // 如果有URL绑定 则进行绑定检测 + $type = substr($bind, 0, 1); + $bind = substr($bind, 1); + + $bindTo = [ + '\\' => 'bindToClass', + '@' => 'bindToController', + ':' => 'bindToNamespace', + ]; + + if (isset($bindTo[$type])) { + return $this->{$bindTo[$type]}($request, $url, $bind); + } + } + + return false; + } + + protected function parseBindAppendParam(&$bind) + { + if (false !== strpos($bind, '?')) { + list($bind, $query) = explode('?', $bind); + parse_str($query, $vars); + $this->append($vars); + } + } + + /** + * 绑定到类 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @return CallbackDispatch + */ + protected function bindToClass($request, $url, $class) + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($request, $array[1], $param); + } + + return new CallbackDispatch($request, $this, [$class, $action], $param); + } + + /** + * 绑定到命名空间 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @return CallbackDispatch + */ + protected function bindToNamespace($request, $url, $namespace) + { + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller'); + $method = !empty($array[1]) ? $array[1] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[2])) { + $this->parseUrlParams($request, $array[2], $param); + } + + return new CallbackDispatch($request, $this, [$namespace . '\\' . Loader::parseName($class, 1), $method], $param); + } + + /** + * 绑定到控制器类 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @return ControllerDispatch + */ + protected function bindToController($request, $url, $controller) + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($request, $array[1], $param); + } + + return new ControllerDispatch($request, $this, $controller . '/' . $action, $param); + } + + /** + * 绑定到模块/控制器 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @return ModuleDispatch + */ + protected function bindToModule($request, $url, $controller) + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($request, $array[1], $param); + } + + return new ModuleDispatch($request, $this, $controller . '/' . $action, $param); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/Resource.php b/Server/application/common/Server/thinkphp/library/think/route/Resource.php new file mode 100644 index 00000000..ff139282 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/Resource.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Route; + +class Resource extends RuleGroup +{ + // 资源路由名称 + protected $resource; + + // REST路由方法定义 + protected $rest = []; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 资源名称 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param array $rest 资源定义 + */ + public function __construct(Route $router, RuleGroup $parent = null, $name = '', $route = '', $option = [], $pattern = [], $rest = []) + { + $this->router = $router; + $this->parent = $parent; + $this->resource = $name; + $this->route = $route; + $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; + + $this->setFullName(); + + // 资源路由默认为完整匹配 + $option['complete_match'] = true; + + $this->pattern = $pattern; + $this->option = $option; + $this->rest = $rest; + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->buildResourceRule(); + } + } + + /** + * 生成资源路由规则 + * @access protected + * @return void + */ + protected function buildResourceRule() + { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + $rule = $this->resource; + $option = $this->option; + + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + + foreach ($array as $val) { + $item[] = $val . '/<' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id') . '>'; + } + + $rule = implode('/', $item) . '/' . $last; + } + + $prefix = substr($rule, strlen($this->name) + 1); + + // 注册资源路由 + foreach ($this->rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + + if (isset($last) && strpos($val[1], '') && isset($option['var'][$last])) { + $val[1] = str_replace('', '<' . $option['var'][$last] . '>', $val[1]); + } elseif (strpos($val[1], '') && isset($option['var'][$rule])) { + $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]); + } + + $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]); + } + + $this->router->setGroup($origin); + } + + /** + * rest方法定义和修改 + * @access public + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/Rule.php b/Server/application/common/Server/thinkphp/library/think/route/Rule.php new file mode 100644 index 00000000..996305f7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/Rule.php @@ -0,0 +1,1130 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Request; +use think\Response; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; +use think\route\dispatch\Module as ModuleDispatch; +use think\route\dispatch\Redirect as RedirectDispatch; +use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\View as ViewDispatch; + +abstract class Rule +{ + /** + * 路由标识 + * @var string + */ + protected $name; + + /** + * 路由对象 + * @var Route + */ + protected $router; + + /** + * 路由所属分组 + * @var RuleGroup + */ + protected $parent; + + /** + * 路由规则 + * @var mixed + */ + protected $rule; + + /** + * 路由地址 + * @var string|\Closure + */ + protected $route; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 路由变量 + * @var array + */ + protected $vars = []; + + /** + * 路由参数 + * @var array + */ + protected $option = []; + + /** + * 路由变量规则 + * @var array + */ + protected $pattern = []; + + /** + * 需要和分组合并的路由参数 + * @var array + */ + protected $mergeOptions = ['after', 'model', 'header', 'response', 'append', 'middleware']; + + /** + * 是否需要后置操作 + * @var bool + */ + protected $doAfter; + + /** + * 是否锁定参数 + * @var bool + */ + protected $lockOption = false; + + abstract public function check($request, $url, $completeMatch = false); + + /** + * 获取Name + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 获取当前路由规则 + * @access public + * @return string + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取当前路由地址 + * @access public + * @return mixed + */ + public function getRoute() + { + return $this->route; + } + + /** + * 获取当前路由的请求类型 + * @access public + * @return string + */ + public function getMethod() + { + return strtolower($this->method); + } + + /** + * 获取当前路由的变量 + * @access public + * @return array + */ + public function getVars() + { + return $this->vars; + } + + /** + * 获取路由对象 + * @access public + * @return Route + */ + public function getRouter() + { + return $this->router; + } + + /** + * 路由是否有后置操作 + * @access public + * @return bool + */ + public function doAfter() + { + return $this->doAfter; + } + + /** + * 获取路由分组 + * @access public + * @return RuleGroup|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取路由所在域名 + * @access public + * @return string + */ + public function getDomain() + { + return $this->parent->getDomain(); + } + + /** + * 获取变量规则定义 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getPattern($name = '') + { + if ('' === $name) { + return $this->pattern; + } + + return isset($this->pattern[$name]) ? $this->pattern[$name] : null; + } + + /** + * 获取路由参数 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getConfig($name = '') + { + return $this->router->config($name); + } + + /** + * 获取路由参数定义 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOption($name = '') + { + if ('' === $name) { + return $this->option; + } + + return isset($this->option[$name]) ? $this->option[$name] : null; + } + + /** + * 注册路由参数 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value = '') + { + if (is_array($name)) { + $this->option = array_merge($this->option, $name); + } else { + $this->option[$name] = $value; + } + + return $this; + } + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this + */ + public function pattern($name, $rule = '') + { + if (is_array($name)) { + $this->pattern = array_merge($this->pattern, $name); + } else { + $this->pattern[$name] = $rule; + } + + return $this; + } + + /** + * 设置标识 + * @access public + * @param string $name 标识名 + * @return $this + */ + public function name($name) + { + $this->name = $name; + + return $this; + } + + /** + * 设置变量 + * @access public + * @param array $vars 变量 + * @return $this + */ + public function vars($vars) + { + $this->vars = $vars; + + return $this; + } + + /** + * 设置路由请求类型 + * @access public + * @param string $method + * @return $this + */ + public function method($method) + { + return $this->option('method', strtolower($method)); + } + + /** + * 设置路由前置行为 + * @access public + * @param array|\Closure $before + * @return $this + */ + public function before($before) + { + return $this->option('before', $before); + } + + /** + * 设置路由后置行为 + * @access public + * @param array|\Closure $after + * @return $this + */ + public function after($after) + { + return $this->option('after', $after); + } + + /** + * 检查后缀 + * @access public + * @param string $ext + * @return $this + */ + public function ext($ext = '') + { + return $this->option('ext', $ext); + } + + /** + * 检查禁止后缀 + * @access public + * @param string $ext + * @return $this + */ + public function denyExt($ext = '') + { + return $this->option('deny_ext', $ext); + } + + /** + * 检查域名 + * @access public + * @param string $domain + * @return $this + */ + public function domain($domain) + { + return $this->option('domain', $domain); + } + + /** + * 设置参数过滤检查 + * @access public + * @param string|array $name + * @param mixed $value + * @return $this + */ + public function filter($name, $value = null) + { + if (is_array($name)) { + $this->option['filter'] = $name; + } else { + $this->option['filter'][$name] = $value; + } + + return $this; + } + + /** + * 绑定模型 + * @access public + * @param array|string $var 路由变量名 多个使用 & 分割 + * @param string|\Closure $model 绑定模型类 + * @param bool $exception 是否抛出异常 + * @return $this + */ + public function model($var, $model = null, $exception = true) + { + if ($var instanceof \Closure) { + $this->option['model'][] = $var; + } elseif (is_array($var)) { + $this->option['model'] = $var; + } elseif (is_null($model)) { + $this->option['model']['id'] = [$var, true]; + } else { + $this->option['model'][$var] = [$model, $exception]; + } + + return $this; + } + + /** + * 附加路由隐式参数 + * @access public + * @param array $append + * @return $this + */ + public function append(array $append = []) + { + if (isset($this->option['append'])) { + $this->option['append'] = array_merge($this->option['append'], $append); + } else { + $this->option['append'] = $append; + } + + return $this; + } + + /** + * 绑定验证 + * @access public + * @param mixed $validate 验证器类 + * @param string $scene 验证场景 + * @param array $message 验证提示 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($validate, $scene = null, $message = [], $batch = false) + { + $this->option['validate'] = [$validate, $scene, $message, $batch]; + + return $this; + } + + /** + * 绑定Response对象 + * @access public + * @param mixed $response + * @return $this + */ + public function response($response) + { + $this->option['response'][] = $response; + return $this; + } + + /** + * 设置Response Header信息 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($header, $value = null) + { + if (is_array($header)) { + $this->option['header'] = $header; + } else { + $this->option['header'][$header] = $value; + } + + return $this; + } + + /** + * 指定路由中间件 + * @access public + * @param string|array|\Closure $middleware + * @param mixed $param + * @return $this + */ + public function middleware($middleware, $param = null) + { + if (is_null($param) && is_array($middleware)) { + $this->option['middleware'] = $middleware; + } else { + foreach ((array) $middleware as $item) { + $this->option['middleware'][] = [$item, $param]; + } + } + + return $this; + } + + /** + * 设置路由缓存 + * @access public + * @param array|string $cache + * @return $this + */ + public function cache($cache) + { + return $this->option('cache', $cache); + } + + /** + * 检查URL分隔符 + * @access public + * @param bool $depr + * @return $this + */ + public function depr($depr) + { + return $this->option('param_depr', $depr); + } + + /** + * 是否合并额外参数 + * @access public + * @param bool $merge + * @return $this + */ + public function mergeExtraVars($merge = true) + { + return $this->option('merge_extra_vars', $merge); + } + + /** + * 设置需要合并的路由参数 + * @access public + * @param array $option + * @return $this + */ + public function mergeOptions($option = []) + { + $this->mergeOptions = array_merge($this->mergeOptions, $option); + return $this; + } + + /** + * 检查是否为HTTPS请求 + * @access public + * @param bool $https + * @return $this + */ + public function https($https = true) + { + return $this->option('https', $https); + } + + /** + * 检查是否为AJAX请求 + * @access public + * @param bool $ajax + * @return $this + */ + public function ajax($ajax = true) + { + return $this->option('ajax', $ajax); + } + + /** + * 检查是否为PJAX请求 + * @access public + * @param bool $pjax + * @return $this + */ + public function pjax($pjax = true) + { + return $this->option('pjax', $pjax); + } + + /** + * 检查是否为手机访问 + * @access public + * @param bool $mobile + * @return $this + */ + public function mobile($mobile = true) + { + return $this->option('mobile', $mobile); + } + + /** + * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量 + * @access public + * @param bool|array $view + * @return $this + */ + public function view($view = true) + { + return $this->option('view', $view); + } + + /** + * 当前路由为重定向 + * @access public + * @param bool $redirect 是否为重定向 + * @return $this + */ + public function redirect($redirect = true) + { + return $this->option('redirect', $redirect); + } + + /** + * 设置路由完整匹配 + * @access public + * @param bool $match + * @return $this + */ + public function completeMatch($match = true) + { + return $this->option('complete_match', $match); + } + + /** + * 是否去除URL最后的斜线 + * @access public + * @param bool $remove + * @return $this + */ + public function removeSlash($remove = true) + { + return $this->option('remove_slash', $remove); + } + + /** + * 设置是否允许跨域 + * @access public + * @param bool $allow + * @param array $header + * @return $this + */ + public function allowCrossDomain($allow = true, $header = []) + { + if (!empty($header)) { + $this->header($header); + } + + if ($allow && $this->parent) { + $this->parent->addRuleItem($this, 'options'); + } + + return $this->option('cross_domain', $allow); + } + + /** + * 检查OPTIONS请求 + * @access public + * @param Request $request + * @return Dispatch|void + */ + protected function checkCrossDomain($request) + { + if (!empty($this->option['cross_domain'])) { + $header = [ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE', + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With', + ]; + + if (!empty($this->option['header'])) { + $header = array_merge($header, $this->option['header']); + } + + if (!isset($header['Access-Control-Allow-Origin'])) { + $httpOrigin = $request->header('origin'); + + if ($httpOrigin && strpos(config('cookie.domain'), $httpOrigin)) { + $header['Access-Control-Allow-Origin'] = $httpOrigin; + } else { + $header['Access-Control-Allow-Origin'] = '*'; + } + } + + $this->option['header'] = $header; + + if ($request->method(true) == 'OPTIONS') { + return new ResponseDispatch($request, $this, Response::create()->code(204)->header($header)); + } + } + } + + /** + * 设置路由规则全局有效 + * @access public + * @return $this + */ + public function crossDomainRule() + { + if ($this instanceof RuleGroup) { + $method = '*'; + } else { + $method = $this->method; + } + + $this->router->setCrossDomainRule($this, $method); + + return $this; + } + + /** + * 合并分组参数 + * @access public + * @return array + */ + public function mergeGroupOptions() + { + if (!$this->lockOption) { + $parentOption = $this->parent->getOption(); + // 合并分组参数 + foreach ($this->mergeOptions as $item) { + if (isset($parentOption[$item]) && isset($this->option[$item])) { + $this->option[$item] = array_merge($parentOption[$item], $this->option[$item]); + } + } + + $this->option = array_merge($parentOption, $this->option); + $this->lockOption = true; + } + + return $this->option; + } + + /** + * 解析匹配到的规则路由 + * @access public + * @param Request $request 请求对象 + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @return Dispatch + */ + public function parseRule($request, $rule, $route, $url, $option = [], $matches = []) + { + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + $search = $replace = []; + + foreach ($matches as $key => $value) { + $search[] = '<' . $key . '>'; + $replace[] = $value; + + $search[] = ':' . $key; + $replace[] = $value; + } + + $route = str_replace($search, $replace, $route); + } + + // 解析额外参数 + $count = substr_count($rule, '/'); + $url = array_slice(explode('|', $url), $count + 1); + $this->parseUrlParams($request, implode('|', $url), $matches); + + $this->vars = $matches; + $this->option = $option; + $this->doAfter = true; + + // 发起路由调度 + return $this->dispatch($request, $route, $option); + } + + /** + * 检查路由前置行为 + * @access protected + * @param mixed $before 前置行为 + * @return mixed + */ + protected function checkBefore($before) + { + $hook = Container::get('hook'); + + foreach ((array) $before as $behavior) { + $result = $hook->exec($behavior); + + if (false === $result) { + return false; + } + } + } + + /** + * 发起路由调度 + * @access protected + * @param Request $request Request对象 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @return Dispatch + */ + protected function dispatch($request, $route, $option) + { + if ($route instanceof \Closure) { + // 执行闭包 + $result = new CallbackDispatch($request, $this, $route); + } elseif ($route instanceof Response) { + $result = new ResponseDispatch($request, $this, $route); + } elseif (isset($option['view']) && false !== $option['view']) { + $result = new ViewDispatch($request, $this, $route, is_array($option['view']) ? $option['view'] : []); + } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = new RedirectDispatch($request, $this, $route, [], isset($option['status']) ? $option['status'] : 301); + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + $result = $this->dispatchMethod($request, $route); + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $result = $this->dispatchController($request, substr($route, 1)); + } else { + // 路由到模块/控制器/操作 + $result = $this->dispatchModule($request, $route); + } + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return CallbackDispatch + */ + protected function dispatchMethod($request, $route) + { + list($path, $var) = $this->parseUrlPath($route); + + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + + return new CallbackDispatch($request, $this, $method, $var); + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return ControllerDispatch + */ + protected function dispatchController($request, $route) + { + list($route, $var) = $this->parseUrlPath($route); + + $result = new ControllerDispatch($request, $this, implode('/', $route), $var); + + $request->setAction(array_pop($route)); + $request->setController($route ? array_pop($route) : $this->getConfig('default_controller')); + $request->setModule($route ? array_pop($route) : $this->getConfig('default_module')); + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return ModuleDispatch + */ + protected function dispatchModule($request, $route) + { + list($path, $var) = $this->parseUrlPath($route); + + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = $this->getConfig('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = $request->method(); + + if ($this->getConfig('use_action_prefix') && $this->router->getMethodPrefix($method)) { + $prefix = $this->router->getMethodPrefix($method); + // 操作方法前缀支持 + $action = 0 !== strpos($action, $prefix) ? $prefix . $action : $action; + } + + // 设置当前请求的路由变量 + $request->setRouteVars($var); + + // 路由到模块/控制器/操作 + return new ModuleDispatch($request, $this, [$module, $controller, $action], ['convert' => false]); + } + + /** + * 路由检查 + * @access protected + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + protected function checkOption($option, Request $request) + { + // 请求类型检测 + if (!empty($option['method'])) { + if (is_string($option['method']) && false === stripos($option['method'], $request->method())) { + return false; + } + } + + // AJAX PJAX 请求检查 + foreach (['ajax', 'pjax', 'mobile'] as $item) { + if (isset($option[$item])) { + $call = 'is' . $item; + if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) { + return false; + } + } + } + + // 伪静态后缀检测 + if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) { + return false; + } + + // 域名检查 + if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) { + return false; + } + + // HTTPS检查 + if ((isset($option['https']) && $option['https'] && !$request->isSsl()) + || (isset($option['https']) && !$option['https'] && $request->isSsl())) { + return false; + } + + // 请求参数检查 + if (isset($option['filter'])) { + foreach ($option['filter'] as $name => $value) { + if ($request->param($name, '', null) != $value) { + return false; + } + } + } + return true; + } + + /** + * 解析URL地址中的参数Request对象 + * @access protected + * @param Request $request + * @param string $rule 路由规则 + * @param array $var 变量 + * @return void + */ + protected function parseUrlParams($request, $url, &$var = []) + { + if ($url) { + if ($this->getConfig('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + } + + /** + * 解析URL的pathinfo参数和变量 + * @access public + * @param string $url URL地址 + * @return array + */ + public function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } elseif (false !== strpos($url, '=')) { + // 参数1=值1&参数2=值2... + $path = []; + parse_str($url, $var); + } else { + $path = [$url]; + } + + return [$path, $var]; + } + + /** + * 生成路由的正则规则 + * @access protected + * @param string $rule 路由规则 + * @param array $match 匹配的变量 + * @param array $pattern 路由变量规则 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildRuleRegex($rule, $match, $pattern = [], $option = [], $completeMatch = false, $suffix = '') + { + foreach ($match as $name) { + $replace[] = $this->buildNameRegex($name, $pattern, $suffix); + } + + // 是否区分 / 地址访问 + if ('/' != $rule) { + if (!empty($option['remove_slash'])) { + $rule = rtrim($rule, '/'); + } elseif (substr($rule, -1) == '/') { + $rule = rtrim($rule, '/'); + $hasSlash = true; + } + } + + $regex = str_replace(array_unique($match), array_unique($replace), $rule); + $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); + + if (isset($hasSlash)) { + $regex .= '\/'; + } + + return $regex . ($completeMatch ? '$' : ''); + } + + /** + * 生成路由变量的正则规则 + * @access protected + * @param string $name 路由变量 + * @param string $pattern 变量规则 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildNameRegex($name, $pattern, $suffix) + { + $optional = ''; + $slash = substr($name, 0, 1); + + if (in_array($slash, ['/', '-'])) { + $prefix = '\\' . $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } + + if ('<' != $slash) { + return $prefix . preg_quote($name, '/'); + } + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } + + if (isset($pattern[$name])) { + $nameRule = $pattern[$name]; + if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) { + $nameRule = substr($nameRule, 1, -1); + } + } else { + $nameRule = $this->getConfig('default_route_pattern'); + } + + return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; + } + + /** + * 分析路由规则中的变量 + * @access protected + * @param string $rule 路由规则 + * @return array + */ + protected function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + + if (preg_match_all('/<\w+\??>/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } else { + $name = substr($name, 1, -1); + } + + $var[$name] = $optional ? 2 : 1; + } + } + + return $var; + } + + /** + * 设置路由参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return $this + */ + public function __call($method, $args) + { + if (count($args) > 1) { + $args[0] = $args; + } + array_unshift($args, $method); + + return call_user_func_array([$this, 'option'], $args); + } + + public function __sleep() + { + return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern', 'doAfter']; + } + + public function __wakeup() + { + $this->router = Container::get('route'); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['parent'], $data['router'], $data['route']); + + return $data; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/RuleGroup.php b/Server/application/common/Server/thinkphp/library/think/route/RuleGroup.php new file mode 100644 index 00000000..5781d8cf --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/RuleGroup.php @@ -0,0 +1,601 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Exception; +use think\Request; +use think\Response; +use think\Route; +use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\Url as UrlDispatch; + +class RuleGroup extends Rule +{ + // 分组路由(包括子分组) + protected $rules = [ + '*' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'patch' => [], + 'delete' => [], + 'head' => [], + 'options' => [], + ]; + + // MISS路由 + protected $miss; + + // 自动路由 + protected $auto; + + // 完整名称 + protected $fullName; + + // 所在域名 + protected $domain; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 分组名称 + * @param mixed $rule 分组路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, RuleGroup $parent = null, $name = '', $rule = [], $option = [], $pattern = []) + { + $this->router = $router; + $this->parent = $parent; + $this->rule = $rule; + $this->name = trim($name, '/'); + $this->option = $option; + $this->pattern = $pattern; + + $this->setFullName(); + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this); + } + + if ($router->isTest()) { + $this->lazy(false); + } + } + + /** + * 设置分组的路由规则 + * @access public + * @return void + */ + protected function setFullName() + { + if (false !== strpos($this->name, ':')) { + $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name); + } + + if ($this->parent && $this->parent->getFullName()) { + $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); + } else { + $this->fullName = $this->name; + } + } + + /** + * 获取所属域名 + * @access public + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + // 跨域OPTIONS请求 + if ($dispatch = $this->checkCrossDomain($request)) { + return $dispatch; + } + + // 检查分组有效性 + if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) { + return false; + } + + // 检查前置行为 + if (isset($this->option['before'])) { + if (false === $this->checkBefore($this->option['before'])) { + return false; + } + unset($this->option['before']); + } + + // 解析分组路由 + if ($this instanceof Resource) { + $this->buildResourceRule(); + } elseif ($this->rule) { + if ($this->rule instanceof Response) { + return new ResponseDispatch($request, $this, $this->rule); + } + + $this->parseGroupRule($this->rule); + } + + // 获取当前路由规则 + $method = strtolower($request->method()); + $rules = $this->getMethodRules($method); + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + // 合并分组变量规则 + $this->pattern = array_merge($this->parent->getPattern(), $this->pattern); + } + + if (isset($this->option['complete_match'])) { + $completeMatch = $this->option['complete_match']; + } + + if (!empty($this->option['merge_rule_regex'])) { + // 合并路由正则规则进行路由匹配检查 + $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + // 检查分组路由 + foreach ($rules as $key => $item) { + $result = $item->check($request, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + if ($this->auto) { + // 自动解析URL地址 + $result = new UrlDispatch($request, $this, $this->auto . '/' . $url, ['auto_search' => false]); + } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { + // 未匹配所有路由的路由规则处理 + $result = $this->miss->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->mergeGroupOptions()); + } else { + $result = false; + } + + return $result; + } + + /** + * 获取当前请求的路由规则(包括子分组、资源路由) + * @access protected + * @param string $method + * @return array + */ + protected function getMethodRules($method) + { + return array_merge($this->rules[$method], $this->rules['*']); + } + + /** + * 分组URL匹配检查 + * @access protected + * @param string $url + * @return bool + */ + protected function checkUrl($url) + { + if ($this->fullName) { + $pos = strpos($this->fullName, '<'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + return true; + } + + /** + * 延迟解析分组的路由规则 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + if (!$lazy) { + $this->parseGroupRule($this->rule); + $this->rule = null; + } + + return $this; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access public + * @param mixed $rule 路由规则 + * @return void + */ + public function parseGroupRule($rule) + { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_array($rule)) { + $this->addRules($rule); + } elseif (is_string($rule) && $rule) { + $this->router->bind($rule, $this->domain); + } + + $this->router->setGroup($origin); + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param array $rules 路由规则 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + protected function checkMergeRuleRegex($request, &$rules, $url, $completeMatch) + { + $depr = $this->router->config('pathinfo_depr'); + $url = $depr . str_replace('|', $depr, $url); + + foreach ($rules as $key => $item) { + if ($item instanceof RuleItem) { + $rule = $depr . str_replace('/', $depr, $item->getRule()); + if ($depr == $rule && $depr != $url) { + unset($rules[$key]); + continue; + } + + $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $item->checkRule($request, $url, []); + } + + unset($rules[$key]); + continue; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + unset($rules[$key]); + continue; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + unset($rules[$key]); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); + + $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + $items[$key] = $item; + } + } + } + + if (empty($regex)) { + return false; + } + + try { + $result = preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match); + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + if ($result) { + $var = []; + foreach ($match as $key => $val) { + if (is_string($key) && '' !== $val) { + list($name, $pos) = explode('_THINK_', $key); + + $var[$name] = $val; + } + } + + if (!isset($pos)) { + foreach ($regex as $key => $item) { + if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { + $pos = $key; + break; + } + } + } + + $rule = $items[$pos]->getRule(); + $array = $this->router->getRule($rule); + + foreach ($array as $item) { + if (in_array($item->getMethod(), ['*', strtolower($request->method())])) { + $result = $item->checkRule($request, $url, $var); + + if (false !== $result) { + return $result; + } + } + } + } + + return false; + } + + /** + * 获取分组的MISS路由 + * @access public + * @return RuleItem|null + */ + public function getMissRule() + { + return $this->miss; + } + + /** + * 获取分组的自动路由 + * @access public + * @return string + */ + public function getAutoRule() + { + return $this->auto; + } + + /** + * 注册自动路由 + * @access public + * @param string $route 路由规则 + * @return void + */ + public function addAutoRule($route) + { + $this->auto = $route; + } + + /** + * 注册MISS路由 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem + */ + public function addMissRule($route, $method = '*', $option = []) + { + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method), $option); + + $this->miss = $ruleItem; + + return $ruleItem; + } + + /** + * 添加分组下的路由规则或者子分组 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return $this + */ + public function addRule($rule, $route, $method = '*', $option = [], $pattern = []) + { + // 读取路由标识 + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } else { + $name = null; + } + + $method = strtolower($method); + + if ('/' === $rule || '' === $rule) { + // 首页自动完整匹配 + $rule .= '$'; + } + + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern); + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($ruleItem, $method); + } + + $this->addRuleItem($ruleItem, $method); + + return $ruleItem; + } + + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function addRules($rules, $method = '*', $option = [], $pattern = []) + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + + if (is_array($val)) { + $route = array_shift($val); + $option = $val ? array_shift($val) : []; + $pattern = $val ? array_shift($val) : []; + } else { + $route = $val; + } + + $this->addRule($key, $route, $method, $option, $pattern); + } + } + + public function addRuleItem($rule, $method = '*') + { + if (strpos($method, '|')) { + $rule->method($method); + $method = '*'; + } + + $this->rules[$method][] = $rule; + + return $this; + } + + /** + * 设置分组的路由前缀 + * @access public + * @param string $prefix + * @return $this + */ + public function prefix($prefix) + { + if ($this->parent && $this->parent->getOption('prefix')) { + $prefix = $this->parent->getOption('prefix') . $prefix; + } + + return $this->option('prefix', $prefix); + } + + /** + * 设置资源允许 + * @access public + * @param array $only + * @return $this + */ + public function only($only) + { + return $this->option('only', $only); + } + + /** + * 设置资源排除 + * @access public + * @param array $except + * @return $this + */ + public function except($except) + { + return $this->option('except', $except); + } + + /** + * 设置资源路由的变量 + * @access public + * @param array $vars + * @return $this + */ + public function vars($vars) + { + return $this->option('var', $vars); + } + + /** + * 合并分组的路由规则正则 + * @access public + * @param bool $merge + * @return $this + */ + public function mergeRuleRegex($merge = true) + { + return $this->option('merge_rule_regex', $merge); + } + + /** + * 获取完整分组Name + * @access public + * @return string + */ + public function getFullName() + { + return $this->fullName; + } + + /** + * 获取分组的路由规则 + * @access public + * @param string $method + * @return array + */ + public function getRules($method = '') + { + if ('' === $method) { + return $this->rules; + } + + return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : []; + } + + /** + * 清空分组下的路由规则 + * @access public + * @return void + */ + public function clear() + { + $this->rules = [ + '*' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'patch' => [], + 'delete' => [], + 'head' => [], + 'options' => [], + ]; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/RuleItem.php b/Server/application/common/Server/thinkphp/library/think/route/RuleItem.php new file mode 100644 index 00000000..a05d2deb --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/RuleItem.php @@ -0,0 +1,292 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Exception; +use think\Route; + +class RuleItem extends Rule +{ + protected $hasSetRule; + + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由标识 + * @param string|array $rule 路由规则 + * @param string|\Closure $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, RuleGroup $parent, $name, $rule, $route, $method = '*', $option = [], $pattern = []) + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->method = $method; + $this->option = $option; + $this->pattern = $pattern; + + $this->setRule($rule); + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this, $method); + } + } + + /** + * 路由规则预处理 + * @access public + * @param string $rule 路由规则 + * @return void + */ + public function setRule($rule) + { + if ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $rule = substr($rule, 0, -1); + + $this->option['complete_match'] = true; + } + + $rule = '/' != $rule ? ltrim($rule, '/') : ''; + + if ($this->parent && $prefix = $this->parent->getFullName()) { + $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + if (false !== strpos($rule, ':')) { + $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule); + } else { + $this->rule = $rule; + } + + // 生成路由标识的快捷访问 + $this->setRuleName(); + } + + /** + * 检查后缀 + * @access public + * @param string $ext + * @return $this + */ + public function ext($ext = '') + { + $this->option('ext', $ext); + $this->setRuleName(true); + + return $this; + } + + /** + * 设置别名 + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + $this->setRuleName(true); + + return $this; + } + + /** + * 设置路由标识 用于URL反解生成 + * @access protected + * @param bool $first 是否插入开头 + * @return void + */ + protected function setRuleName($first = false) + { + if ($this->name) { + $vars = $this->parseVar($this->rule); + $name = strtolower($this->name); + + if (isset($this->option['ext'])) { + $suffix = $this->option['ext']; + } elseif ($this->parent->getOption('ext')) { + $suffix = $this->parent->getOption('ext'); + } else { + $suffix = null; + } + + $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix, $this->method]; + + Container::get('rule_name')->set($name, $value, $first); + } + + if (!$this->hasSetRule) { + Container::get('rule_name')->setRule($this->rule, $this); + $this->hasSetRule = true; + } + } + + /** + * 检测路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $match 匹配路由变量 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function checkRule($request, $url, $match = null, $completeMatch = false) + { + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + // 合并分组参数 + $option = $this->mergeGroupOptions(); + + $url = $this->urlSuffixCheck($request, $url, $option); + + if (is_null($match)) { + $match = $this->match($url, $option, $completeMatch); + } + + if (false !== $match) { + if (!empty($option['cross_domain'])) { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + $option['header'] = $this->option['header']; + } + + // 检查前置行为 + if (isset($option['before']) && false === $this->checkBefore($option['before'])) { + return false; + } + + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); + } + + return false; + } + + /** + * 检测路由(含路由匹配) + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + return $this->checkRule($request, $url, null, $completeMatch); + } + + /** + * URL后缀及Slash检查 + * @access protected + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $option 路由参数 + * @return string + */ + protected function urlSuffixCheck($request, $url, $option = []) + { + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + return $url; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @return array|false + */ + private function match($url, $option, $completeMatch) + { + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + $pattern = array_merge($this->parent->getPattern(), $this->pattern); + $depr = $this->router->config('pathinfo_depr'); + + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + + $var = []; + $url = $depr . str_replace('|', $depr, $url); + $rule = $depr . str_replace('/', $depr, $this->rule); + + if ($depr == $rule && $depr != $url) { + return false; + } + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) { + return $var; + } + return false; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + return false; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); + + try { + if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/u', $url, $match)) { + return false; + } + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + foreach ($match as $key => $val) { + if (is_string($key)) { + $var[$key] = $val; + } + } + } + + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/RuleName.php b/Server/application/common/Server/thinkphp/library/think/route/RuleName.php new file mode 100644 index 00000000..202fb0e2 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/RuleName.php @@ -0,0 +1,147 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +class RuleName +{ + protected $item = []; + protected $rule = []; + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param array $value 路由规则 + * @param bool $first 是否置顶 + * @return void + */ + public function set($name, $value, $first = false) + { + if ($first && isset($this->item[$name])) { + array_unshift($this->item[$name], $value); + } else { + $this->item[$name][] = $value; + } + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $route 路由 + * @return void + */ + public function setRule($rule, $route) + { + $this->rule[$route->getDomain()][$rule][$route->getMethod()] = $route; + } + + /** + * 根据路由规则获取路由对象(列表) + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @return array + */ + public function getRule($rule, $domain = null) + { + return isset($this->rule[$domain][$rule]) ? $this->rule[$domain][$rule] : []; + } + + /** + * 获取全部路由列表 + * @access public + * @param string $domain 域名 + * @return array + */ + public function getRuleList($domain = null) + { + $list = []; + + foreach ($this->rule as $ruleDomain => $rules) { + foreach ($rules as $rule => $items) { + foreach ($items as $item) { + $val['domain'] = $ruleDomain; + + foreach (['method', 'rule', 'name', 'route', 'pattern', 'option'] as $param) { + $call = 'get' . $param; + $val[$param] = $item->$call(); + } + + $list[$ruleDomain][] = $val; + } + } + } + + if ($domain) { + return isset($list[$domain]) ? $list[$domain] : []; + } + + return $list; + } + + /** + * 导入路由标识 + * @access public + * @param array $name 路由标识 + * @return void + */ + public function import($item) + { + $this->item = $item; + } + + /** + * 根据路由标识获取路由信息(用于URL生成) + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @return array|null + */ + public function get($name = null, $domain = null, $method = '*') + { + if (is_null($name)) { + return $this->item; + } + + $name = strtolower($name); + $method = strtolower($method); + + if (isset($this->item[$name])) { + if (is_null($domain)) { + $result = $this->item[$name]; + } else { + $result = []; + foreach ($this->item[$name] as $item) { + if ($item[2] == $domain && ('*' == $item[4] || $method == $item[4])) { + $result[] = $item; + } + } + } + } else { + $result = null; + } + + return $result; + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear() + { + $this->item = []; + $this->rule = []; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/dispatch/Callback.php b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Callback.php new file mode 100644 index 00000000..ca76fc99 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Callback.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\route\Dispatch; + +class Callback extends Dispatch +{ + public function exec() + { + // 执行回调方法 + $vars = array_merge($this->request->param(), $this->param); + + return $this->app->invoke($this->dispatch, $vars); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/dispatch/Controller.php b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Controller.php new file mode 100644 index 00000000..1de82992 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Controller.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\route\Dispatch; + +class Controller extends Dispatch +{ + public function exec() + { + // 执行控制器的操作方法 + $vars = array_merge($this->request->param(), $this->param); + + return $this->app->action( + $this->dispatch, $vars, + $this->rule->getConfig('url_controller_layer'), + $this->rule->getConfig('controller_suffix') + ); + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/dispatch/Module.php b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Module.php new file mode 100644 index 00000000..40bd7759 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Module.php @@ -0,0 +1,138 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use ReflectionMethod; +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\Loader; +use think\Request; +use think\route\Dispatch; + +class Module extends Dispatch +{ + protected $controller; + protected $actionName; + + public function init() + { + parent::init(); + + $result = $this->dispatch; + + if (is_string($result)) { + $result = explode('/', $result); + } + + if ($this->rule->getConfig('app_multi_module')) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $this->rule->getConfig('default_module'))); + $bind = $this->rule->getRouter()->getBind(); + $available = false; + + if ($bind && preg_match('/^[a-z]/is', $bind)) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + if (empty($result[0])) { + $module = $bindModule; + } + $available = true; + } elseif (!in_array($module, $this->rule->getConfig('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { + $available = true; + } elseif ($this->rule->getConfig('empty_module')) { + $module = $this->rule->getConfig('empty_module'); + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $this->request->setModule($module); + $this->app->init($module); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } + + // 是否自动转换控制器和操作名 + $convert = is_bool($this->convert) ? $this->convert : $this->rule->getConfig('url_convert'); + // 获取控制器名 + $controller = strip_tags($result[1] ?: $this->rule->getConfig('default_controller')); + + $this->controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $this->actionName = strip_tags($result[2] ?: $this->rule->getConfig('default_action')); + + // 设置当前请求的控制器、操作 + $this->request + ->setController(Loader::parseName($this->controller, 1)) + ->setAction($this->actionName); + + return $this; + } + + public function exec() + { + // 监听module_init + $this->app['hook']->listen('module_init'); + + try { + // 实例化控制器 + $instance = $this->app->controller($this->controller, + $this->rule->getConfig('url_controller_layer'), + $this->rule->getConfig('controller_suffix'), + $this->rule->getConfig('empty_controller')); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + $this->app['middleware']->controller(function (Request $request, $next) use ($instance) { + // 获取当前操作名 + $action = $this->actionName . $this->rule->getConfig('action_suffix'); + + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + + // 严格获取当前操作方法名 + $reflect = new ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $this->rule->getConfig('action_suffix'); + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $this->request->setAction($actionName); + + // 自动获取请求变量 + $vars = $this->rule->getConfig('url_param_type') + ? $this->request->route() + : $this->request->param(); + $vars = array_merge($vars, $this->param); + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$this->actionName]; + $reflect = new ReflectionMethod($instance, '_empty'); + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + $this->app['hook']->listen('action_begin', $call); + + $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); + + return $this->autoResponse($data); + }); + + return $this->app['middleware']->dispatch($this->request, 'controller'); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/dispatch/Redirect.php b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Redirect.php new file mode 100644 index 00000000..fae2c9a6 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Redirect.php @@ -0,0 +1,23 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Response; +use think\route\Dispatch; + +class Redirect extends Dispatch +{ + public function exec() + { + return Response::create($this->dispatch, 'redirect')->code($this->code); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/dispatch/Response.php b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Response.php new file mode 100644 index 00000000..66f4e5ab --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Response.php @@ -0,0 +1,23 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\route\Dispatch; + +class Response extends Dispatch +{ + public function exec() + { + return $this->dispatch; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/dispatch/Url.php b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Url.php new file mode 100644 index 00000000..acc524e3 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/dispatch/Url.php @@ -0,0 +1,169 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\exception\HttpException; +use think\Loader; +use think\route\Dispatch; + +class Url extends Dispatch +{ + public function init() + { + // 解析默认的URL规则 + $result = $this->parseUrl($this->dispatch); + + return (new Module($this->request, $this->rule, $result))->init(); + } + + public function exec() + {} + + /** + * 解析URL地址 + * @access protected + * @param string $url URL + * @return array + */ + protected function parseUrl($url) + { + $depr = $this->rule->getConfig('pathinfo_depr'); + $bind = $this->rule->getRouter()->getBind(); + + if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { + $bind = str_replace('/', $depr, $bind); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + + list($path, $var) = $this->rule->parseUrlPath($url); + if (empty($path)) { + return [null, null, null]; + } + + // 解析模块 + $module = $this->rule->getConfig('app_multi_module') ? array_shift($path) : null; + + if ($this->param['auto_search']) { + $controller = $this->autoFindController($module, $path); + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + + if ($controller && !preg_match('/^[A-Za-z0-9][\w|\.]*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + + // 解析额外参数 + if ($path) { + if ($this->rule->getConfig('url_param_type')) { + $var += $path; + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, implode('|', $path)); + } + } + + $panDomain = $this->request->panDomain(); + + if ($panDomain && $key = array_search('*', $var)) { + // 泛域名赋值 + $var[$key] = $panDomain; + } + + // 设置当前请求的参数 + $this->request->setRouteVars($var); + + // 封装路由 + $route = [$module, $controller, $action]; + + if ($this->hasDefinedRoute($route, $bind)) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + + return $route; + } + + /** + * 检查URL是否已经定义过路由 + * @access protected + * @param string $route 路由信息 + * @param string $bind 绑定信息 + * @return bool + */ + protected function hasDefinedRoute($route, $bind) + { + list($module, $controller, $action) = $route; + + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + + $name2 = ''; + + if (empty($module) || $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + $host = $this->request->host(true); + + $method = $this->request->method(); + + if ($this->rule->getRouter()->getName($name, $host, $method) || $this->rule->getRouter()->getName($name2, $host, $method)) { + return true; + } + + return false; + } + + /** + * 自动定位控制器类 + * @access protected + * @param string $module 模块名 + * @param array $path URL + * @return string + */ + protected function autoFindController($module, &$path) + { + $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->rule->getConfig('url_controller_layer'); + $suffix = $this->app->getSuffix() || $this->rule->getConfig('controller_suffix') ? ucfirst($this->rule->getConfig('url_controller_layer')) : ''; + + $item = []; + $find = false; + + foreach ($path as $val) { + $item[] = $val; + $file = $dir . '/' . str_replace('.', '/', $val) . $suffix . '.php'; + $file = pathinfo($file, PATHINFO_DIRNAME) . '/' . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . '.php'; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= '/' . Loader::parseName($val); + } + } + + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + + return $controller; + } + +} diff --git a/Server/application/common/Server/thinkphp/library/think/route/dispatch/View.php b/Server/application/common/Server/thinkphp/library/think/route/dispatch/View.php new file mode 100644 index 00000000..ea3ef11b --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/route/dispatch/View.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Response; +use think\route\Dispatch; + +class View extends Dispatch +{ + public function exec() + { + // 渲染模板输出 + $vars = array_merge($this->request->param(), $this->param); + + return Response::create($this->dispatch, 'view')->assign($vars); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/session/driver/Memcache.php b/Server/application/common/Server/thinkphp/library/think/session/driver/Memcache.php new file mode 100644 index 00000000..40d7bb82 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/session/driver/Memcache.php @@ -0,0 +1,124 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandlerInterface; +use think\Exception; + +class Memcache implements SessionHandlerInterface +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'persistent' => true, // 长连接 + 'session_name' => '', // memcache key前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcache')) { + throw new Exception('not support:memcache'); + } + + $this->handler = new \Memcache; + + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->config['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : + $this->handler->addServer($host, $port, $this->config['persistent'], 1); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/session/driver/Memcached.php b/Server/application/common/Server/thinkphp/library/think/session/driver/Memcached.php new file mode 100644 index 00000000..074b2ff7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/session/driver/Memcached.php @@ -0,0 +1,135 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandlerInterface; +use think\Exception; + +class Memcached implements SessionHandlerInterface +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'session_name' => '', // memcache key前缀 + 'username' => '', //账号 + 'password' => '', //密码 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcached')) { + throw new Exception('not support:memcached'); + } + + $this->handler = new \Memcached; + + // 设置连接超时时间(单位:毫秒) + if ($this->config['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); + } + + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + + $this->handler->addServers($servers); + + if ('' != $this->config['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->quit(); + $this->handler = null; + + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/session/driver/Redis.php b/Server/application/common/Server/thinkphp/library/think/session/driver/Redis.php new file mode 100644 index 00000000..5a0e7bc7 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/session/driver/Redis.php @@ -0,0 +1,179 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandlerInterface; +use think\Exception; + +class Redis implements SessionHandlerInterface +{ + /** @var \Redis */ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // redis主机 + 'port' => 6379, // redis端口 + 'password' => '', // 密码 + 'select' => 0, // 操作库 + 'expire' => 3600, // 有效期(秒) + 'timeout' => 0, // 超时时间(秒) + 'persistent' => true, // 是否长连接 + 'session_name' => '', // sessionkey前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + * @return bool + * @throws Exception + */ + public function open($savePath, $sessName) + { + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->config as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication'])) { + $params[$key] = $val; + unset($this->config[$key]); + } + } + $this->handler = new \Predis\Client($this->config, $params); + } else { + throw new \BadFunctionCallException('not support: redis'); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + if ($this->config['expire'] > 0) { + $result = $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + } else { + $result = $this->handler->set($this->config['session_name'] . $sessID, $sessData); + } + + return $result ? true : false; + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->del($this->config['session_name'] . $sessID) > 0; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return bool + */ + public function gc($sessMaxLifeTime) + { + return true; + } + + /** + * Redis Session 驱动的加锁机制 + * @access public + * @param string $sessID 用于加锁的sessID + * @param integer $timeout 默认过期时间 + * @return bool + */ + public function lock($sessID, $timeout = 10) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $lockKey = 'LOCK_PREFIX_' . $sessID; + // 使用setnx操作加锁 + $isLock = $this->handler->setnx($lockKey, 1); + if ($isLock) { + // 设置过期时间,防止死任务的出现 + $this->handler->expire($lockKey, $timeout); + return true; + } + + return false; + } + + /** + * Redis Session 驱动的解锁机制 + * @access public + * @param string $sessID 用于解锁的sessID + */ + public function unlock($sessID) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $this->handler->del('LOCK_PREFIX_' . $sessID); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/template/TagLib.php b/Server/application/common/Server/thinkphp/library/think/template/TagLib.php new file mode 100644 index 00000000..bbbb2c03 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/template/TagLib.php @@ -0,0 +1,351 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use think\Exception; + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib +{ + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = []; // 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib = ''; + + /** + * 标签库标签列表 + * @var array + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var array + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var bool + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + protected $tpl; + + protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; + + /** + * 架构函数 + * @access public + * @param \stdClass $template 模板引擎对象 + */ + public function __construct($template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $tags = []; + $lib = $lib ? strtolower($lib) . ':' : ''; + + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $tags[$close][$lib . $name] = $name; + if (isset($val['alias'])) { + // 别名设置 + $array = (array) $val['alias']; + foreach (explode(',', $array[0]) as $v) { + $tags[$close][$lib . $v] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ('' == $match[1][0]) { + $name = strtolower($match[2][0]); + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0], // 标签结束符 + ]; + } + } else { + // 标签头压入栈 + $right[strtolower($match[1][0])][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name, $alias); + $method = 'tag' . $name; + + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break)); + + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { + // 对应的标签名 + $name = $tags[0][strtolower($matches[1])]; + $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($matches[0], $name, $alias); + $method = 'tag' . $name; + return $this->$method($attrs, ''); + }, $content); + } + + return; + } + + /** + * 按标签生成正则 + * @access public + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, $close) + { + $begin = $this->tpl->config('taglib_begin'); + $end = $this->tpl->config('taglib_end'); + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $tagName = is_array($tags) ? implode('|', $tags) : $tags; + + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + + return '/' . $regex . '/is'; + } + + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 + * @return array + */ + public function parseAttr($str, $name, $alias = '') + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $result = []; + + if (preg_match_all($regex, $str, $matches)) { + foreach ($matches['name'] as $key => $val) { + $result[$val] = $matches['value'][$key]; + } + + if (!isset($this->tags[$name])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias'])) { + $array = (array) $val['alias']; + if (in_array($name, explode(',', $array[0]))) { + $tag = $val; + $type = !empty($array[1]) ? $array[1] : 'type'; + $result[$type] = $name; + break; + } + } + } + } else { + $tag = $this->tags[$name]; + // 设置了标签别名 + if (!empty($alias) && isset($tag['alias'])) { + $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type'; + $result[$type] = $alias; + } + } + + if (!empty($tag['must'])) { + $must = explode(',', $tag['must']); + foreach ($must as $name) { + if (!isset($result[$name])) { + throw new Exception('tag attr must:' . $name); + } + } + } + } else { + // 允许直接使用表达式的标签 + if (!empty($this->tags[$name]['expression'])) { + static $_taglibs; + if (!isset($_taglibs[$name])) { + $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin')); + } + $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); + } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { + throw new Exception('tag error:' . $name); + } + } + + return $result; + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return string + */ + public function parseCondition($condition) + { + if (!strpos($condition, '::') && strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(&$name) + { + $flag = substr($name, 0, 1); + + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { + // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; + } + + // 不以$开头并且也不是常量,自动补上$前缀 + $name = '$' . $name; + } + + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name, false); + + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + public function getTags() + { + return $this->tags; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/template/driver/File.php b/Server/application/common/Server/thinkphp/library/think/template/driver/File.php new file mode 100644 index 00000000..3b96a0f3 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/template/driver/File.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use think\Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void|array + */ + public function write($cacheFile, $content) + { + // 检测模板目录 + $dir = dirname($cacheFile); + + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + + // 生成模板缓存文件 + if (false === file_put_contents($cacheFile, $content)) { + throw new Exception('cache write error:' . $cacheFile, 11602); + } + } + + /** + * 读取编译编译 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read($cacheFile, $vars = []) + { + $this->cacheFile = $cacheFile; + + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return boolean + */ + public function check($cacheFile, $cacheTime) + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + + if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + + return true; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/template/taglib/Cx.php b/Server/application/common/Server/thinkphp/library/think/template/taglib/Cx.php new file mode 100644 index 00000000..ad741f28 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/template/taglib/Cx.php @@ -0,0 +1,724 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\taglib; + +use think\template\TagLib; + +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends Taglib +{ + + // 标签定义 + protected $tags = [ + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => ['attr' => ''], + 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], + 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], + 'if' => ['attr' => 'condition', 'expression' => true], + 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true], + 'else' => ['attr' => '', 'close' => 0], + 'switch' => ['attr' => 'name', 'expression' => true], + 'case' => ['attr' => 'value,break', 'expression' => true], + 'default' => ['attr' => '', 'close' => 0], + 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']], + 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']], + 'empty' => ['attr' => 'name'], + 'notempty' => ['attr' => 'name'], + 'present' => ['attr' => 'name'], + 'notpresent' => ['attr' => 'name'], + 'defined' => ['attr' => 'name'], + 'notdefined' => ['attr' => 'name'], + 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']], + 'assign' => ['attr' => 'name,value', 'close' => 0], + 'define' => ['attr' => 'name,value', 'close' => 0], + 'for' => ['attr' => 'start,end,name,comparison,step'], + 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true], + 'function' => ['attr' => 'name,vars,use,call'], + ]; + + /** + * php标签解析 + * 格式: + * {php}echo $name{/php} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPhp($tag, $content) + { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * {volist name="userList" id="user" empty=""} + * {user.username} + * {user.email} + * {/volist} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagVolist($tag, $content) + { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $key = !empty($tag['key']) ? $tag['key'] : 'i'; + $mod = isset($tag['mod']) ? $tag['mod'] : '2'; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + $parseStr .= '$_result=' . $name . ';'; + $name = '$_result'; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $parseStr .= ' $__LIST__ = ' . $name . ';'; + } + + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; + $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; + $parseStr .= '++$' . $key . ';?>'; + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + + return; + } + + /** + * foreach标签解析 循环输出数据集 + * 格式: + * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} + * {user.username} + * {/foreach} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagForeach($tag, $content) + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + $name = $tag['name']; + $key = !empty($tag['key']) ? $tag['key'] : 'key'; + $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + + $parseStr = 'autoBuildVar($name); + $parseStr .= $var . '=' . $name . '; '; + $name = $var; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + if (!isset($var)) { + $var = '$_' . uniqid(); + } + $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $var = &$name; + } + + $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + $parseStr .= '$' . $index . '=0; '; + } + + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + if (isset($tag['mod'])) { + $mod = (int) $tag['mod']; + $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; + } + $parseStr .= '++$' . $index . '; '; + } + + $parseStr .= '?>'; + // 循环体中的内容 + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + + return; + } + + /** + * if标签解析 + * 格式: + * {if condition=" $a eq 1"} + * {elseif condition="$a eq 2" /} + * {else /} + * {/if} + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagIf($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse($tag) + { + $parseStr = ''; + + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * {switch name="a.name"} + * {case value="1" break="false"}1{/case} + * {case value="2" }2{/case} + * {default /}other + * {/switch} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagSwitch($tag, $content) + { + $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCase($tag, $content) + { + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $value = 'case ' . $value . ':'; + } elseif (strpos($value, '|')) { + $values = explode('|', $value); + $value = ''; + foreach ($values as $val) { + $value .= 'case "' . addslashes($val) . '":'; + } + } else { + $value = 'case "' . $value . '":'; + } + + $parseStr = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefault($tag) + { + $parseStr = ''; + + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: {compare name="" type="eq" value="" }content{/compare} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCompare($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } else { + $value = '\'' . $value . '\''; + } + + switch ($type) { + case 'equal': + $type = 'eq'; + break; + case 'notequal': + $type = 'neq'; + break; + } + $type = $this->parseCondition(' ' . $type . ' '); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} + * example: {range name="a" value="1,2,3" type='in' }content{/range} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagRange($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 + + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; + } else { + $value = '"' . $value . '"'; + $str = 'explode(\',\',' . $value . ')'; + } + + if ('between' == $type) { + $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; + } elseif ('notbetween' == $type) { + $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; + } else { + $fun = ('in' == $type) ? 'in_array' : '!in_array'; + $parseStr = '' . $content . ''; + } + + return $parseStr; + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: {present name="" }content{/present} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: {notpresent name="" }content{/notpresent} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotpresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: {empty name="" }content{/empty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagEmpty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty())): ?>' . $content . ''; + + return $parseStr; + } + + /** + * notempty标签解析 + * 如果某个变量不为empty 则输出内容 + * 格式: {notempty name="" }content{/notempty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotempty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * load 标签解析 {load file="/static/js/base.js" /} + * 格式:{load file="/static/css/base.css" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagLoad($tag, $content) + { + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + + $parseStr = ''; + $endStr = ''; + + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; + $parseStr .= ''; + $endStr = ''; + } + + // 文件方式导入 + $array = explode(',', $file); + + foreach ($array as $val) { + $type = strtolower(substr(strrchr($val, '.'), 1)); + switch ($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + + return $parseStr . $endStr; + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: {assign name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagAssign($tag, $content) + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine($tag, $content) + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor($tag, $content) + { + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + + //获取属性 + foreach ($tag as $key => $value) { + $value = trim($value); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } + + switch ($key) { + case 'start': + $start = $value; + break; + case 'end': + $end = $value; + break; + case 'step': + $step = $value; + break; + case 'comparison': + $comparison = $value; + break; + case 'name': + $name = $value; + break; + } + } + + $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + + return $parseStr; + } + + /** + * url函数的tag标签 + * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagUrl($tag, $content) + { + $url = isset($tag['link']) ? $tag['link'] : ''; + $vars = isset($tag['vars']) ? $tag['vars'] : ''; + $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; + $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + + return ''; + } + + /** + * function标签解析 匿名函数,可实现递归 + * 使用: + * {function name="func" vars="$data" call="$list" use="&$a,&$b"} + * {if is_array($data)} + * {foreach $data as $val} + * {~func($val) /} + * {/foreach} + * {else /} + * {$data} + * {/if} + * {/function} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFunction($tag, $content) + { + $name = !empty($tag['name']) ? $tag['name'] : 'func'; + $vars = !empty($tag['vars']) ? $tag['vars'] : ''; + $call = !empty($tag['call']) ? $tag['call'] : ''; + $use = ['&$' . $name]; + + if (!empty($tag['use'])) { + foreach (explode(',', $tag['use']) as $val) { + $use[] = '&' . ltrim(trim($val), '&'); + } + } + + $parseStr = '' . $content . '' : '?>'; + + return $parseStr; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/validate/ValidateRule.php b/Server/application/common/Server/thinkphp/library/think/validate/ValidateRule.php new file mode 100644 index 00000000..7cd70174 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/validate/ValidateRule.php @@ -0,0 +1,171 @@ + +// +---------------------------------------------------------------------- + +namespace think\validate; + +/** + * Class ValidateRule + * @package think\validate + * @method ValidateRule confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateRule different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateRule egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateRule gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateRule elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateRule notIn(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateRule max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateRule min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateRule after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateRule allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateRule denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isRequire(mixed $rule = null, string $msg = '') static 验证字段必须 + * @method ValidateRule isNumber(mixed $rule = null, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isArray(mixed $rule = null, string $msg = '') static 验证字段值是否为数组 + * @method ValidateRule isInteger(mixed $rule = null, string $msg = '') static 验证字段值是否为整形 + * @method ValidateRule isFloat(mixed $rule = null, string $msg = '') static 验证字段值是否为浮点数 + * @method ValidateRule isMobile(mixed $rule = null, string $msg = '') static 验证字段值是否为手机 + * @method ValidateRule isIdCard(mixed $rule = null, string $msg = '') static 验证字段值是否为身份证号码 + * @method ValidateRule isChs(mixed $rule = null, string $msg = '') static 验证字段值是否为中文 + * @method ValidateRule isChsDash(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母及下划线 + * @method ValidateRule isChsAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为中文和字母 + * @method ValidateRule isChsAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母和数字 + * @method ValidateRule isDate(mixed $rule = null, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isBool(mixed $rule = null, string $msg = '') static 验证字段值是否为布尔值 + * @method ValidateRule isAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为字母 + * @method ValidateRule isAlphaDash(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和下划线 + * @method ValidateRule isAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和数字 + * @method ValidateRule isAccepted(mixed $rule = null, string $msg = '') static 验证字段值是否为yes, on, 或是 1 + * @method ValidateRule isEmail(mixed $rule = null, string $msg = '') static 验证字段值是否为有效邮箱格式 + * @method ValidateRule isUrl(mixed $rule = null, string $msg = '') static 验证字段值是否为有效URL地址 + * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateRule fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateRule fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateRule image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateRule method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateRule dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateRule unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateRule behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateRule filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule must(mixed $rule = null, string $msg = '') static 必须验证 + */ +class ValidateRule +{ + // 验证字段的名称 + protected $title; + + // 当前验证规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + + /** + * 添加验证因子 + * @access protected + * @param string $name 验证名称 + * @param mixed $rule 验证规则 + * @param string $msg 提示信息 + * @return $this + */ + protected function addItem($name, $rule = null, $msg = '') + { + if ($rule || 0 === $rule) { + $this->rule[$name] = $rule; + } else { + $this->rule[] = $name; + } + + $this->message[] = $msg; + + return $this; + } + + /** + * 获取验证规则 + * @access public + * @return array + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取验证字段名称 + * @access public + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * 获取验证提示 + * @access public + * @return array + */ + public function getMsg() + { + return $this->message; + } + + /** + * 设置验证字段名称 + * @access public + * @return $this + */ + public function title($title) + { + $this->title = $title; + + return $this; + } + + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$this, 'addItem'], $args); + } + + public static function __callStatic($method, $args) + { + $rule = new static(); + + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$rule, 'addItem'], $args); + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/view/driver/Php.php b/Server/application/common/Server/thinkphp/library/think/view/driver/Php.php new file mode 100644 index 00000000..7948dc05 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/view/driver/Php.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; + +class Php +{ + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + ]; + + protected $template; + protected $app; + protected $content; + + public function __construct(App $app, $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch($template, $data = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + $this->template = $template; + + // 记录视图信息 + $this->app + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + + extract($data, EXTR_OVERWRITE); + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display($content, $data = []) + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + if (empty($this->config['view_path'])) { + $this->config['view_path'] = $this->app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; + } + + $request = $this->app['request']; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); + } else { + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return void + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return isset($this->config[$name]) ? $this->config[$name] : null; + } else { + $this->config[$name] = $value; + } + } + + public function __debugInfo() + { + return ['config' => $this->config]; + } +} diff --git a/Server/application/common/Server/thinkphp/library/think/view/driver/Think.php b/Server/application/common/Server/thinkphp/library/think/view/driver/Think.php new file mode 100644 index 00000000..877aee85 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/think/view/driver/Think.php @@ -0,0 +1,192 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Template; + +class Think +{ + // 模板引擎实例 + private $template; + private $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + ]; + + public function __construct(App $app, $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + + if (empty($this->config['view_path'])) { + $this->config['view_path'] = $app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; + } + + $this->template = new Template($app, $this->config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $data = [], $config = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + // 记录视图信息 + $this->app + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + + $this->template->fetch($template, $data, $config); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($template, $data = [], $config = []) + { + $this->template->display($template, $data, $config); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + // 分析模板文件规则 + $request = $this->app['request']; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); + } else { + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + + /** + * 配置或者获取模板引擎参数 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->template->config($name); + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return $this->template->config($name); + } else { + $this->template->$name = $value; + $this->config[$name] = $value; + } + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } + + public function __debugInfo() + { + return ['config' => $this->config]; + } +} diff --git a/Server/application/common/Server/thinkphp/library/traits/controller/Jump.php b/Server/application/common/Server/thinkphp/library/traits/controller/Jump.php new file mode 100644 index 00000000..41f7e930 --- /dev/null +++ b/Server/application/common/Server/thinkphp/library/traits/controller/Jump.php @@ -0,0 +1,168 @@ +error(); + * $this->redirect(); + * } + * } + */ +namespace traits\controller; + +use think\Container; +use think\exception\HttpResponseException; +use think\Response; +use think\response\Redirect; + +trait Jump +{ + /** + * 应用实例 + * @var \think\App + */ + protected $app; + + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @param array $header 发送的Header信息 + * @return void + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { + $url = $_SERVER["HTTP_REFERER"]; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); + } + + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + $type = $this->getResponseType(); + // 把跳转模板的渲染下沉,这样在 response_send 行为里通过getData()获得的数据是一致性的格式 + if ('html' == strtolower($type)) { + $type = 'jump'; + } + + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_success_tmpl')]); + + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @param array $header 发送的Header信息 + * @return void + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + $type = $this->getResponseType(); + if (is_null($url)) { + $url = $this->app['request']->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app['url']->build($url); + } + + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $type = 'jump'; + } + + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_error_tmpl')]); + + throw new HttpResponseException($response); + } + + /** + * 返回封装后的API数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param integer $code 返回的code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的Header信息 + * @return void + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => time(), + 'data' => $data, + ]; + + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL重定向 + * @access protected + * @param string $url 跳转的URL表达式 + * @param array|integer $params 其它URL参数 + * @param integer $code http code + * @param array $with 隐式传参 + * @return void + */ + protected function redirect($url, $params = [], $code = 302, $with = []) + { + $response = new Redirect($url); + + if (is_integer($params)) { + $code = $params; + $params = []; + } + + $response->code($code)->params($params)->with($with); + + throw new HttpResponseException($response); + } + + /** + * 获取当前的response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + if (!$this->app) { + $this->app = Container::get('app'); + } + + $isAjax = $this->app['request']->isAjax(); + $config = $this->app['config']; + + return $isAjax + ? $config->get('default_ajax_return') + : $config->get('default_return_type'); + } +} diff --git a/Server/application/common/Server/thinkphp/logo.png b/Server/application/common/Server/thinkphp/logo.png new file mode 100644 index 00000000..25fd0593 Binary files /dev/null and b/Server/application/common/Server/thinkphp/logo.png differ diff --git a/Server/application/common/Server/thinkphp/phpunit.xml.dist b/Server/application/common/Server/thinkphp/phpunit.xml.dist new file mode 100644 index 00000000..37c3d2b5 --- /dev/null +++ b/Server/application/common/Server/thinkphp/phpunit.xml.dist @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + ./library/think/*/tests/ + + + + + + ./library/ + + ./library/think/*/tests + ./library/think/*/assets + ./library/think/*/resources + ./library/think/*/vendor + + + + \ No newline at end of file diff --git a/Server/application/common/Server/thinkphp/tpl/default_index.tpl b/Server/application/common/Server/thinkphp/tpl/default_index.tpl new file mode 100644 index 00000000..e5c1363a --- /dev/null +++ b/Server/application/common/Server/thinkphp/tpl/default_index.tpl @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

    :)

    ThinkPHP V5.1
    12载初心不改(2006-2018) - 你值得信赖的PHP框架

    '; + } +} diff --git a/Server/application/common/Server/thinkphp/tpl/dispatch_jump.tpl b/Server/application/common/Server/thinkphp/tpl/dispatch_jump.tpl new file mode 100644 index 00000000..583376bb --- /dev/null +++ b/Server/application/common/Server/thinkphp/tpl/dispatch_jump.tpl @@ -0,0 +1,49 @@ +{__NOLAYOUT__} + + + + + 跳转提示 + + + +
    + + +

    :)

    +

    + + +

    :(

    +

    + + +

    +

    + 页面自动 跳转 等待时间: +

    +
    + + + diff --git a/Server/application/common/Server/thinkphp/tpl/page_trace.tpl b/Server/application/common/Server/thinkphp/tpl/page_trace.tpl new file mode 100644 index 00000000..2e5afbab --- /dev/null +++ b/Server/application/common/Server/thinkphp/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
    + + +
    +
    +
    getUseTime().'s ';?>
    + +
    + + diff --git a/Server/application/common/Server/thinkphp/tpl/think_exception.tpl b/Server/application/common/Server/thinkphp/tpl/think_exception.tpl new file mode 100644 index 00000000..c8988859 --- /dev/null +++ b/Server/application/common/Server/thinkphp/tpl/think_exception.tpl @@ -0,0 +1,508 @@ +'.end($names).''; + } + } + + if(!function_exists('parse_file')){ + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } + } + + if(!function_exists('parse_args')){ + function parse_args($args) + { + $result = []; + + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%s)', parse_class(get_class($item))); + break; + case is_array($item): + if(count($item) > 3){ + $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); + } else { + $value = sprintf('[%s]', parse_args($item)); + } + break; + case is_string($item): + if(strlen($item) > 20){ + $value = sprintf( + '\'%s...\'', + htmlentities($item), + htmlentities(substr($item, 0, 20)) + ); + } else { + $value = sprintf("'%s'", htmlentities($item)); + } + break; + case is_int($item): + case is_float($item): + $value = $item; + break; + case is_null($item): + $value = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } + } +?> + + + + + 系统发生错误 + + + + + +
    + +
    + +
    +
    + +
    +
    +

    [

    +
    +

    +
    + +
    + +
    +
      $value) { ?>
    +
    + +
    +

    Call Stack

    +
      +
    1. + +
    2. + +
    3. + +
    +
    +
    + +
    + +

    + +
    + + + +
    +

    Exception Datas

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + +
    +

    Environment Variables

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + + + + + + diff --git a/Server/thinkphp/.gitignore b/Server/thinkphp/.gitignore index 7e31ef51..f7775ba4 100644 --- a/Server/thinkphp/.gitignore +++ b/Server/thinkphp/.gitignore @@ -1,4 +1,8 @@ -/composer.lock /vendor -.idea +composer.phar +composer.lock .DS_Store +Thumbs.db +/phpunit.xml +/.idea +/.vscode \ No newline at end of file diff --git a/Server/thinkphp/CONTRIBUTING.md b/Server/thinkphp/CONTRIBUTING.md index dc8e91cd..6cefcb38 100644 --- a/Server/thinkphp/CONTRIBUTING.md +++ b/Server/thinkphp/CONTRIBUTING.md @@ -7,7 +7,7 @@ ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 -参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 我们希望你贡献的代码符合: @@ -60,7 +60,7 @@ GitHub 提供了 Issue 功能,该功能可以用于: 6. 变基(衍合 `rebase`)你的分支到上游 master 分支; 7. `push` 你的本地仓库到 GitHub; 8. 提交 `pull request`; -9. 等待 CI 验证(若不通过则重复 5~7,不需要重新提交 `pull request`,GitHub 会自动更新你的 `pull request`); +9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`); 10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 *若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* diff --git a/Server/thinkphp/LICENSE.txt b/Server/thinkphp/LICENSE.txt index 2cb9a8a9..774fa76f 100644 --- a/Server/thinkphp/LICENSE.txt +++ b/Server/thinkphp/LICENSE.txt @@ -1,6 +1,6 @@ ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 -版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn) +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) All rights reserved。 ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 diff --git a/Server/thinkphp/README.md b/Server/thinkphp/README.md index f01fd2b9..1339e6c7 100644 --- a/Server/thinkphp/README.md +++ b/Server/thinkphp/README.md @@ -1,103 +1,88 @@ -ThinkPHP 5.0 +![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) + +ThinkPHP 5.1(LTS) —— 12载初心,你值得信赖的PHP框架 =============== -[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=5.1)](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1) [![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) -[![codecov.io](http://codecov.io/github/top-think/framework/coverage.svg?branch=master)](http://codecov.io/github/github/top-think/framework?branch=master) [![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) [![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) -[![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework) +[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-8892BF.svg)](http://www.php.net/) [![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) -ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括: +ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: - + 基于命名空间和众多PHP新特性 - + 核心功能组件化 - + 强化路由功能 - + 更灵活的控制器 - + 重构的模型和数据库类 - + 配置文件可分离 - + 重写的自动验证和完成 - + 简化扩展机制 - + API支持完善 - + 改进的Log类 - + 命令行访问支持 - + REST支持 - + 引导文件支持 - + 方便的自动生成定义 - + 真正惰性加载 - + 分布式环境支持 - + 支持Composer - + 支持MongoDb + + 采用容器统一管理对象 + + 支持Facade + + 更易用的路由 + + 注解路由支持 + + 路由跨域请求支持 + + 验证类增强 + + 配置和路由目录独立 + + 取消系统常量 + + 类库别名机制 + + 模型和数据库增强 + + 依赖注入完善 + + 支持PSR-3日志规范 + + 中间件支持(`V5.1.6+`) + + 支持`Swoole`/`Workerman`运行(`V5.1.18+`) -> ThinkPHP5的运行环境要求PHP5.4以上。 +官方已经正式宣布`5.1.27`版本为LTS版本。 -详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) +### 废除的功能: -## 目录结构 + + 聚合模型 + + 内置控制器扩展类 + + 模型自动验证 -初始的目录结构如下: +> ThinkPHP5.1的运行环境要求PHP5.6+ 兼容PHP8.0。 + + +## 安装 + +使用composer安装 ~~~ -www WEB部署目录(或者子目录) -├─application 应用目录 -│ ├─common 公共模块目录(可以更改) -│ ├─module_name 模块目录 -│ │ ├─config.php 模块配置文件 -│ │ ├─common.php 模块函数文件 -│ │ ├─controller 控制器目录 -│ │ ├─model 模型目录 -│ │ ├─view 视图目录 -│ │ └─ ... 更多类库目录 -│ │ -│ ├─command.php 命令行工具配置文件 -│ ├─common.php 公共函数文件 -│ ├─config.php 公共配置文件 -│ ├─route.php 路由配置文件 -│ ├─tags.php 应用行为扩展定义文件 -│ └─database.php 数据库配置文件 -│ -├─public WEB目录(对外访问目录) -│ ├─index.php 入口文件 -│ ├─router.php 快速测试文件 -│ └─.htaccess 用于apache的重写 -│ -├─thinkphp 框架系统目录 -│ ├─lang 语言文件目录 -│ ├─library 框架类库目录 -│ │ ├─think Think类库包目录 -│ │ └─traits 系统Trait目录 -│ │ -│ ├─tpl 系统模板目录 -│ ├─base.php 基础定义文件 -│ ├─console.php 控制台入口文件 -│ ├─convention.php 框架惯例配置文件 -│ ├─helper.php 助手函数文件 -│ ├─phpunit.xml phpunit配置文件 -│ └─start.php 框架入口文件 -│ -├─extend 扩展类库目录 -├─runtime 应用的运行时目录(可写,可定制) -├─vendor 第三方类库目录(Composer依赖库) -├─build.php 自动生成定义文件(参考) -├─composer.json composer 定义文件 -├─LICENSE.txt 授权说明文件 -├─README.md README 文件 -├─think 命令行入口文件 +composer create-project topthink/think tp ~~~ -> router.php用于php自带webserver支持,可用于快速测试 -> 切换到public目录后,启动命令:php -S localhost:8888 router.php -> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +更新框架 +~~~ +composer update topthink/framework +~~~ + + +## 在线手册 + ++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content) ++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) + + +## 官方服务 + ++ [应用服务市场](https://market.topthink.com/) ++ [ThinkAPI——统一API服务](https://docs.topthink.com/think-api) ## 命名规范 -ThinkPHP5的命名规范遵循`PSR-2`规范以及`PSR-4`自动加载规范。 +`ThinkPHP5.1`遵循PSR-2命名规范和PSR-4自动加载规范。 ## 参与开发 -注册并登录 Github 帐号, fork 本项目并进行改动。 -更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md) +请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。 ## 版权信息 diff --git a/Server/thinkphp/base.php b/Server/thinkphp/base.php index 92c4fa55..d7238cc6 100644 --- a/Server/thinkphp/base.php +++ b/Server/thinkphp/base.php @@ -8,58 +8,45 @@ // +---------------------------------------------------------------------- // | Author: liu21st // +---------------------------------------------------------------------- - -define('THINK_VERSION', '5.0.24'); -define('THINK_START_TIME', microtime(true)); -define('THINK_START_MEM', memory_get_usage()); -define('EXT', '.php'); -define('DS', DIRECTORY_SEPARATOR); -defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); -define('LIB_PATH', THINK_PATH . 'library' . DS); -define('CORE_PATH', LIB_PATH . 'think' . DS); -define('TRAIT_PATH', LIB_PATH . 'traits' . DS); -defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); -defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); -defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); -defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); -defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); -defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); -defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); -defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); -defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录 -defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀 -defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀 - -// 环境常量 -define('IS_CLI', PHP_SAPI == 'cli' ? true : false); -define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); +namespace think; // 载入Loader类 -require CORE_PATH . 'Loader.php'; - -// 加载环境变量配置文件 -if (is_file(ROOT_PATH . '.env')) { - $env = parse_ini_file(ROOT_PATH . '.env', true); - - foreach ($env as $key => $val) { - $name = ENV_PREFIX . strtoupper($key); - - if (is_array($val)) { - foreach ($val as $k => $v) { - $item = $name . '_' . strtoupper($k); - putenv("$item=$v"); - } - } else { - putenv("$name=$val"); - } - } -} +require __DIR__ . '/library/think/Loader.php'; // 注册自动加载 -\think\Loader::register(); +Loader::register(); // 注册错误和异常处理机制 -\think\Error::register(); +Error::register(); -// 加载惯例配置文件 -\think\Config::set(include THINK_PATH . 'convention' . EXT); +// 实现日志接口 +if (interface_exists('Psr\Log\LoggerInterface')) { + interface LoggerInterface extends \Psr\Log\LoggerInterface + {} +} else { + interface LoggerInterface + {} +} + +// 注册类库别名 +Loader::addClassAlias([ + 'App' => facade\App::class, + 'Build' => facade\Build::class, + 'Cache' => facade\Cache::class, + 'Config' => facade\Config::class, + 'Cookie' => facade\Cookie::class, + 'Db' => Db::class, + 'Debug' => facade\Debug::class, + 'Env' => facade\Env::class, + 'Facade' => Facade::class, + 'Hook' => facade\Hook::class, + 'Lang' => facade\Lang::class, + 'Log' => facade\Log::class, + 'Request' => facade\Request::class, + 'Response' => facade\Response::class, + 'Route' => facade\Route::class, + 'Session' => facade\Session::class, + 'Url' => facade\Url::class, + 'Validate' => facade\Validate::class, + 'View' => facade\View::class, +]); diff --git a/Server/thinkphp/composer.json b/Server/thinkphp/composer.json index ccc81ca2..33477b1d 100644 --- a/Server/thinkphp/composer.json +++ b/Server/thinkphp/composer.json @@ -13,23 +13,23 @@ { "name": "liu21st", "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" } ], "require": { - "php": ">=5.4.0", - "topthink/think-installer": "~1.0" + "php": ">=5.6.0", + "topthink/think-installer": "2.*" }, "require-dev": { - "phpunit/phpunit": "4.8.*", + "phpunit/phpunit": "^5.0|^6.0", "johnkary/phpunit-speedtrap": "^1.0", "mikey179/vfsstream": "~1.6", "phploc/phploc": "2.*", "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "2.*", "phpdocumentor/reflection-docblock": "^2.0" - }, - "autoload": { - "psr-4": { - "think\\": "library/think" - } } } diff --git a/Server/thinkphp/convention.php b/Server/thinkphp/convention.php index 31a0a0c1..1d85e56e 100644 --- a/Server/thinkphp/convention.php +++ b/Server/thinkphp/convention.php @@ -4,118 +4,155 @@ return [ // +---------------------------------------------------------------------- // | 应用设置 // +---------------------------------------------------------------------- - // 默认Host地址 - 'app_host' => '', - // 应用调试模式 - 'app_debug' => false, - // 应用Trace - 'app_trace' => false, - // 应用模式状态 - 'app_status' => '', - // 是否支持多模块 - 'app_multi_module' => true, - // 入口自动绑定模块 - 'auto_bind_module' => false, - // 注册的根命名空间 - 'root_namespace' => [], - // 扩展函数文件 - 'extra_file_list' => [THINK_PATH . 'helper' . EXT], - // 默认输出类型 - 'default_return_type' => 'html', - // 默认AJAX 数据返回格式,可选json xml ... - 'default_ajax_return' => 'json', - // 默认JSONP格式返回的处理方法 - 'default_jsonp_handler' => 'jsonpReturn', - // 默认JSONP处理方法 - 'var_jsonp_handler' => 'callback', - // 默认时区 - 'default_timezone' => 'PRC', - // 是否开启多语言 - 'lang_switch_on' => false, - // 默认全局过滤方法 用逗号分隔多个 - 'default_filter' => '', - // 默认语言 - 'default_lang' => 'zh-cn', - // 应用类库后缀 - 'class_suffix' => false, - // 控制器类后缀 - 'controller_suffix' => false, + 'app' => [ + // 应用名称 + 'app_name' => '', + // 应用地址 + 'app_host' => '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否HTTPS + 'is_https' => false, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认验证器 + 'default_validate' => '', + // 默认语言 + 'default_lang' => 'zh-cn', - // +---------------------------------------------------------------------- - // | 模块设置 - // +---------------------------------------------------------------------- + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- - // 默认模块名 - 'default_module' => 'index', - // 禁止访问模块 - 'deny_module_list' => ['common'], - // 默认控制器名 - 'default_controller' => 'Index', - // 默认操作名 - 'default_action' => 'index', - // 默认验证器 - 'default_validate' => '', - // 默认的空控制器名 - 'empty_controller' => 'Error', - // 操作方法前缀 - 'use_action_prefix' => false, - // 操作方法后缀 - 'action_suffix' => '', - // 自动搜索控制器 - 'controller_auto_search' => false, + // 自动搜索控制器 + 'controller_auto_search' => false, + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 默认的空模块名 + 'empty_module' => '', + // 默认模块名 + 'default_module' => 'index', + // 是否支持多模块 + 'app_multi_module' => true, + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, - // +---------------------------------------------------------------------- - // | URL设置 - // +---------------------------------------------------------------------- + // +---------------------------------------------------------------------- + // | URL请求设置 + // +---------------------------------------------------------------------- - // PATHINFO变量名 用于兼容模式 - 'var_pathinfo' => 's', - // 兼容PATH_INFO获取 - 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], - // pathinfo分隔符 - 'pathinfo_depr' => '/', - // HTTPS代理标识 - 'https_agent_name' => '', - // URL伪静态后缀 - 'url_html_suffix' => 'html', - // URL普通方式参数 用于自动生成 - 'url_common_param' => false, - // URL参数方式 0 按名称成对解析 1 按顺序解析 - 'url_param_type' => 0, - // 是否开启路由 - 'url_route_on' => true, - // 路由配置文件(支持配置多个) - 'route_config_file' => ['route'], - // 路由使用完整匹配 - 'route_complete_match' => false, - // 是否强制使用路由 - 'url_route_must' => false, - // 域名部署 - 'url_domain_deploy' => false, - // 域名根,如thinkphp.cn - 'url_domain_root' => '', - // 是否自动转换URL中的控制器和操作名 - 'url_convert' => true, - // 默认的访问控制器层 - 'url_controller_layer' => 'controller', - // 表单请求类型伪装变量 - 'var_method' => '_method', - // 表单ajax伪装变量 - 'var_ajax' => '_ajax', - // 表单pjax伪装变量 - 'var_pjax' => '_pjax', - // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 - 'request_cache' => false, - // 请求缓存有效期 - 'request_cache_expire' => null, - // 全局请求缓存排除规则 - 'request_cache_except' => [], + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'HTTP_X_REAL_IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 路由设置 + // +---------------------------------------------------------------------- + + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 使用注解路由 + 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '\w+', + // 是否开启路由缓存 + 'route_check_cache' => false, + // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5 + 'route_check_cache_key' => '', + // 路由缓存的设置 + 'route_cache_option' => [], + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + // 异常页面的模板文件 + 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl', + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + ], // +---------------------------------------------------------------------- // | 模板设置 // +---------------------------------------------------------------------- - 'template' => [ + 'template' => [ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 'auto_rule' => 1, // 模板引擎类型 支持 php think 支持扩展 @@ -127,7 +164,7 @@ return [ // 模板后缀 'view_suffix' => 'html', // 模板文件名分隔符 - 'view_depr' => DS, + 'view_depr' => DIRECTORY_SEPARATOR, // 模板引擎普通标签开始标记 'tpl_begin' => '{', // 模板引擎普通标签结束标记 @@ -138,58 +175,42 @@ return [ 'taglib_end' => '}', ], - // 视图输出字符串内容替换 - 'view_replace_str' => [], - // 默认跳转页面对应的模板文件 - 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', - 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', - - // +---------------------------------------------------------------------- - // | 异常及错误设置 - // +---------------------------------------------------------------------- - - // 异常页面的模板文件 - 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', - - // 错误显示信息,非调试模式有效 - 'error_message' => '页面错误!请稍后再试~', - // 显示错误信息 - 'show_error_msg' => false, - // 异常处理handle类 留空使用 \think\exception\Handle - 'exception_handle' => '', - // 是否记录trace信息到日志 - 'record_trace' => false, - // +---------------------------------------------------------------------- // | 日志设置 // +---------------------------------------------------------------------- - 'log' => [ + 'log' => [ // 日志记录方式,内置 file socket 支持扩展 - 'type' => 'File', + 'type' => 'File', // 日志保存目录 - 'path' => LOG_PATH, + //'path' => LOG_PATH, // 日志记录级别 - 'level' => [], + 'level' => [], + // 是否记录trace信息到日志 + 'record_trace' => false, + // 是否JSON格式记录 + 'json' => false, ], // +---------------------------------------------------------------------- // | Trace设置 开启 app_trace 后 有效 // +---------------------------------------------------------------------- - 'trace' => [ + + 'trace' => [ // 内置Html Console 支持扩展 'type' => 'Html', + 'file' => __DIR__ . '/tpl/page_trace.tpl', ], // +---------------------------------------------------------------------- // | 缓存设置 // +---------------------------------------------------------------------- - 'cache' => [ + 'cache' => [ // 驱动方式 'type' => 'File', // 缓存保存目录 - 'path' => CACHE_PATH, + //'path' => CACHE_PATH, // 缓存前缀 'prefix' => '', // 缓存有效期 0表示永久缓存 @@ -200,7 +221,7 @@ return [ // | 会话设置 // +---------------------------------------------------------------------- - 'session' => [ + 'session' => [ 'id' => '', // SESSION_ID的提交变量,解决flash上传跨域 'var_session_id' => '', @@ -217,7 +238,8 @@ return [ // +---------------------------------------------------------------------- // | Cookie设置 // +---------------------------------------------------------------------- - 'cookie' => [ + + 'cookie' => [ // cookie 名称前缀 'prefix' => '', // cookie 保存时间 @@ -238,7 +260,7 @@ return [ // | 数据库设置 // +---------------------------------------------------------------------- - 'database' => [ + 'database' => [ // 数据库类型 'type' => 'mysql', // 数据库连接DSN配置 @@ -279,20 +301,27 @@ return [ 'datetime_format' => 'Y-m-d H:i:s', // 是否需要进行SQL性能分析 'sql_explain' => false, + // 查询对象 + 'query' => '\\think\\db\\Query', ], //分页配置 - 'paginate' => [ + 'paginate' => [ 'type' => 'bootstrap', 'var_page' => 'page', 'list_rows' => 15, ], //控制台配置 - 'console' => [ - 'name' => 'Think Console', - 'version' => '0.1', - 'user' => null, + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + 'auto_path' => '', ], + // 中间件配置 + 'middleware' => [ + 'default_namespace' => 'app\\http\\middleware\\', + ], ]; diff --git a/Server/thinkphp/helper.php b/Server/thinkphp/helper.php index 12683cfd..72b9e9fd 100644 --- a/Server/thinkphp/helper.php +++ b/Server/thinkphp/helper.php @@ -13,205 +13,42 @@ // ThinkPHP 助手函数 //------------------------- -use think\Cache; -use think\Config; -use think\Cookie; +use think\Container; use think\Db; -use think\Debug; use think\exception\HttpException; use think\exception\HttpResponseException; -use think\Lang; -use think\Loader; -use think\Log; -use think\Model; -use think\Request; +use think\facade\Cache; +use think\facade\Config; +use think\facade\Cookie; +use think\facade\Debug; +use think\facade\Env; +use think\facade\Hook; +use think\facade\Lang; +use think\facade\Log; +use think\facade\Request; +use think\facade\Route; +use think\facade\Session; +use think\facade\Url; use think\Response; -use think\Session; -use think\Url; -use think\View; +use think\route\RuleItem; -if (!function_exists('load_trait')) { +if (!function_exists('abort')) { /** - * 快速导入Traits PHP5.5以上无需调用 - * @param string $class trait库 - * @param string $ext 类库后缀 - * @return boolean + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 */ - function load_trait($class, $ext = EXT) + function abort($code, $message = null, $header = []) { - return Loader::import($class, TRAIT_PATH, $ext); - } -} - -if (!function_exists('exception')) { - /** - * 抛出异常处理 - * - * @param string $msg 异常消息 - * @param integer $code 异常代码 默认为0 - * @param string $exception 异常类 - * - * @throws Exception - */ - function exception($msg, $code = 0, $exception = '') - { - $e = $exception ?: '\think\Exception'; - throw new $e($msg, $code); - } -} - -if (!function_exists('debug')) { - /** - * 记录时间(微秒)和内存使用情况 - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer|string $dec 小数位 如果是m 表示统计内存占用 - * @return mixed - */ - function debug($start, $end = '', $dec = 6) - { - if ('' == $end) { - Debug::remark($start); + if ($code instanceof Response) { + throw new HttpResponseException($code); } else { - return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + throw new HttpException($code, $message, null, $header); } } } -if (!function_exists('lang')) { - /** - * 获取语言变量值 - * @param string $name 语言变量名 - * @param array $vars 动态变量值 - * @param string $lang 语言 - * @return mixed - */ - function lang($name, $vars = [], $lang = '') - { - return Lang::get($name, $vars, $lang); - } -} - -if (!function_exists('config')) { - /** - * 获取和设置配置参数 - * @param string|array $name 参数名 - * @param mixed $value 参数值 - * @param string $range 作用域 - * @return mixed - */ - function config($name = '', $value = null, $range = '') - { - if (is_null($value) && is_string($name)) { - return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range); - } else { - return Config::set($name, $value, $range); - } - } -} - -if (!function_exists('input')) { - /** - * 获取输入数据 支持默认值和过滤 - * @param string $key 获取的变量名 - * @param mixed $default 默认值 - * @param string $filter 过滤方法 - * @return mixed - */ - function input($key = '', $default = null, $filter = '') - { - if (0 === strpos($key, '?')) { - $key = substr($key, 1); - $has = true; - } - if ($pos = strpos($key, '.')) { - // 指定参数来源 - list($method, $key) = explode('.', $key, 2); - if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { - $key = $method . '.' . $key; - $method = 'param'; - } - } else { - // 默认为自动判断 - $method = 'param'; - } - if (isset($has)) { - return request()->has($key, $method, $default); - } else { - return request()->$method($key, $default, $filter); - } - } -} - -if (!function_exists('widget')) { - /** - * 渲染输出Widget - * @param string $name Widget名称 - * @param array $data 传入的参数 - * @return mixed - */ - function widget($name, $data = []) - { - return Loader::action($name, $data, 'widget'); - } -} - -if (!function_exists('model')) { - /** - * 实例化Model - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Model - */ - function model($name = '', $layer = 'model', $appendSuffix = false) - { - return Loader::model($name, $layer, $appendSuffix); - } -} - -if (!function_exists('validate')) { - /** - * 实例化验证器 - * @param string $name 验证器名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Validate - */ - function validate($name = '', $layer = 'validate', $appendSuffix = false) - { - return Loader::validate($name, $layer, $appendSuffix); - } -} - -if (!function_exists('db')) { - /** - * 实例化数据库类 - * @param string $name 操作的数据表名称(不含前缀) - * @param array|string $config 数据库配置参数 - * @param bool $force 是否强制重新连接 - * @return \think\db\Query - */ - function db($name = '', $config = [], $force = false) - { - return Db::connect($config, $force)->name($name); - } -} - -if (!function_exists('controller')) { - /** - * 实例化控制器 格式:[模块/]控制器 - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Controller - */ - function controller($name, $layer = 'controller', $appendSuffix = false) - { - return Loader::controller($name, $layer, $appendSuffix); - } -} - if (!function_exists('action')) { /** * 调用模块的操作方法 参数格式 [模块/控制器/]操作 @@ -223,92 +60,186 @@ if (!function_exists('action')) { */ function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) { - return Loader::action($url, $vars, $layer, $appendSuffix); + return app()->action($url, $vars, $layer, $appendSuffix); } } -if (!function_exists('import')) { +if (!function_exists('app')) { /** - * 导入所需的类库 同java的Import 本函数有缓存功能 - * @param string $class 类库命名空间字符串 - * @param string $baseUrl 起始路径 - * @param string $ext 导入的文件扩展名 - * @return boolean + * 快速获取容器中的实例 支持依赖注入 + * @param string $name 类名或标识 默认获取当前应用实例 + * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed|\think\App */ - function import($class, $baseUrl = '', $ext = EXT) + function app($name = 'think\App', $args = [], $newInstance = false) { - return Loader::import($class, $baseUrl, $ext); + return Container::get($name, $args, $newInstance); } } -if (!function_exists('vendor')) { +if (!function_exists('behavior')) { /** - * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 - * @param string $class 类库 - * @param string $ext 类库后缀 - * @return boolean - */ - function vendor($class, $ext = EXT) - { - return Loader::import($class, VENDOR_PATH, $ext); - } -} - -if (!function_exists('dump')) { - /** - * 浏览器友好的变量输出 - * @param mixed $var 变量 - * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 - * @param string $label 标签 默认为空 - * @return void|string - */ - function dump($var, $echo = true, $label = null) - { - return Debug::dump($var, $echo, $label); - } -} - -if (!function_exists('url')) { - /** - * Url生成 - * @param string $url 路由地址 - * @param string|array $vars 变量 - * @param bool|string $suffix 生成的URL后缀 - * @param bool|string $domain 域名 - * @return string - */ - function url($url = '', $vars = '', $suffix = true, $domain = false) - { - return Url::build($url, $vars, $suffix, $domain); - } -} - -if (!function_exists('session')) { - /** - * Session管理 - * @param string|array $name session名称,如果为数组表示进行session设置 - * @param mixed $value session值 - * @param string $prefix 前缀 + * 执行某个行为(run方法) 支持依赖注入 + * @param mixed $behavior 行为类名或者别名 + * @param mixed $args 参数 * @return mixed */ - function session($name, $value = '', $prefix = null) + function behavior($behavior, $args = null) { - if (is_array($name)) { - // 初始化 - Session::init($name); - } elseif (is_null($name)) { - // 清除 - Session::clear('' === $value ? null : $value); - } elseif ('' === $value) { - // 判断或获取 - return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); - } elseif (is_null($value)) { - // 删除 - return Session::delete($name, $prefix); - } else { - // 设置 - return Session::set($name, $value, $prefix); + return Hook::exec($behavior, $args); + } +} + +if (!function_exists('bind')) { + /** + * 绑定一个类到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + function bind($abstract, $concrete = null) + { + return Container::getInstance()->bindTo($abstract, $concrete); + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::rm($name); + } + + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } +} + +if (!function_exists('call')) { + /** + * 调用反射执行callable 支持依赖注入 + * @param mixed $callable 支持闭包等callable写法 + * @param array $args 参数 + * @return mixed + */ + function call($callable, $args = []) + { + return Container::getInstance()->invoke($callable, $args); + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param $class + * @return array + */ + function class_uses_recursive($class) + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + function config($name = '', $value = null) + { + if (is_null($value) && is_string($name)) { + if ('.' == substr($name, -1)) { + return Config::pull(substr($name, 0, -1)); + } + + return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name); + } else { + return Config::set($name, $value); + } + } +} + +if (!function_exists('container')) { + /** + * 获取容器对象实例 + * @return Container + */ + function container() + { + return Container::getInstance(); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return app()->controller($name, $layer, $appendSuffix); } } @@ -330,7 +261,7 @@ if (!function_exists('cookie')) { Cookie::clear($value); } elseif ('' === $value) { // 获取 - return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); } elseif (is_null($value)) { // 删除 return Cookie::delete($name); @@ -341,109 +272,144 @@ if (!function_exists('cookie')) { } } -if (!function_exists('cache')) { +if (!function_exists('db')) { /** - * 缓存管理 - * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 - * @param mixed $value 缓存值 - * @param mixed $options 缓存参数 - * @param string $tag 缓存标签 + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = true) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 * @return mixed */ - function cache($name, $value = '', $options = null, $tag = null) + function debug($start, $end = '', $dec = 6) { - if (is_array($options)) { - // 缓存操作的同时初始化 - $cache = Cache::connect($options); - } elseif (is_array($name)) { - // 缓存初始化 - return Cache::connect($name); + if ('' == $end) { + Debug::remark($start); } else { - $cache = Cache::init(); + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('download')) { + /** + * 获取\think\response\Download对象实例 + * @param string $filename 要下载的文件 + * @param string $name 显示文件名 + * @param bool $content 是否为内容 + * @param integer $expire 有效期(秒) + * @return \think\response\Download + */ + function download($filename, $name = '', $content = false, $expire = 360, $openinBrowser = false) + { + return Response::create($filename, 'download')->name($name)->isContent($content)->expire($expire)->openinBrowser($openinBrowser); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env($name = null, $default = null) + { + return Env::get($name, $default); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; } - if (is_null($name)) { - return $cache->clear($value); - } elseif ('' === $value) { - // 获取缓存 - return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); - } elseif (is_null($value)) { - // 删除缓存 - return $cache->rm($name); - } elseif (0 === strpos($name, '?') && '' !== $value) { - $expire = is_numeric($options) ? $options : null; - return $cache->remember(substr($name, 1), $value, $expire); - } else { - // 缓存数据 - if (is_array($options)) { - $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); } else { - $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + $method = 'param'; } - if (is_null($tag)) { - return $cache->set($name, $value, $expire); - } else { - return $cache->tag($tag)->set($name, $value, $expire); - } - } - } -} - -if (!function_exists('trace')) { - /** - * 记录日志信息 - * @param mixed $log log信息 支持字符串和数组 - * @param string $level 日志级别 - * @return void|array - */ - function trace($log = '[think]', $level = 'log') - { - if ('[think]' === $log) { - return Log::getLog(); } else { - Log::record($log, $level); + // 默认为自动判断 + $method = 'param'; } - } -} -if (!function_exists('request')) { - /** - * 获取当前Request对象实例 - * @return Request - */ - function request() - { - return Request::instance(); - } -} - -if (!function_exists('response')) { - /** - * 创建普通 Response 对象实例 - * @param mixed $data 输出数据 - * @param int|string $code 状态码 - * @param array $header 头信息 - * @param string $type - * @return Response - */ - function response($data = [], $code = 200, $header = [], $type = 'html') - { - return Response::create($data, $type, $code, $header); - } -} - -if (!function_exists('view')) { - /** - * 渲染模板输出 - * @param string $template 模板文件 - * @param array $vars 模板变量 - * @param array $replace 模板替换 - * @param integer $code 状态码 - * @return \think\response\View - */ - function view($template = '', $vars = [], $replace = [], $code = 200) - { - return Response::create($template, 'view', $code)->replace($replace)->assign($vars); + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } } } @@ -477,6 +443,259 @@ if (!function_exists('jsonp')) { } } +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return app()->model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + return Response::create($url, 'redirect', $code)->params($params); + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return app('request'); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = '', $code = 200, $header = [], $type = 'html') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('route')) { + /** + * 路由注册 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + function route($rule, $route, $option = [], $pattern = []) + { + return Route::rule($rule, $route, '*', $option, $pattern); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear($value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::token($name, $type); + + return ''; + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return array|void + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait + * @return array + */ + function trait_uses_recursive($trait) + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return app()->validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param integer $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function view($template = '', $vars = [], $code = 200, $filter = null) + { + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + $result = app()->action($name, $data, 'widget'); + + if (is_object($result)) { + $result = $result->getContent(); + } + + return $result; + } +} + if (!function_exists('xml')) { /** * 获取\think\response\Xml对象实例 @@ -492,98 +711,16 @@ if (!function_exists('xml')) { } } -if (!function_exists('redirect')) { +if (!function_exists('yaconf')) { /** - * 获取\think\response\Redirect对象实例 - * @param mixed $url 重定向地址 支持Url::build方法的地址 - * @param array|integer $params 额外参数 - * @param integer $code 状态码 - * @param array $with 隐式传参 - * @return \think\response\Redirect + * 获取yaconf配置 + * + * @param string $name 配置参数名 + * @param mixed $default 默认值 + * @return mixed */ - function redirect($url = [], $params = [], $code = 302, $with = []) + function yaconf($name, $default = null) { - if (is_integer($params)) { - $code = $params; - $params = []; - } - return Response::create($url, 'redirect', $code)->params($params)->with($with); - } -} - -if (!function_exists('abort')) { - /** - * 抛出HTTP异常 - * @param integer|Response $code 状态码 或者 Response对象实例 - * @param string $message 错误信息 - * @param array $header 参数 - */ - function abort($code, $message = null, $header = []) - { - if ($code instanceof Response) { - throw new HttpResponseException($code); - } else { - throw new HttpException($code, $message, null, $header); - } - } -} - -if (!function_exists('halt')) { - /** - * 调试变量并且中断输出 - * @param mixed $var 调试变量或者信息 - */ - function halt($var) - { - dump($var); - throw new HttpResponseException(new Response); - } -} - -if (!function_exists('token')) { - /** - * 生成表单令牌 - * @param string $name 令牌名称 - * @param mixed $type 令牌生成方法 - * @return string - */ - function token($name = '__token__', $type = 'md5') - { - $token = Request::instance()->token($name, $type); - return ''; - } -} - -if (!function_exists('load_relation')) { - /** - * 延迟预载入关联查询 - * @param mixed $resultSet 数据集 - * @param mixed $relation 关联 - * @return array - */ - function load_relation($resultSet, $relation) - { - $item = current($resultSet); - if ($item instanceof Model) { - $item->eagerlyResultSet($resultSet, $relation); - } - return $resultSet; - } -} - -if (!function_exists('collection')) { - /** - * 数组转换为数据集对象 - * @param array $resultSet 数据集数组 - * @return \think\model\Collection|\think\Collection - */ - function collection($resultSet) - { - $item = current($resultSet); - if ($item instanceof Model) { - return \think\model\Collection::make($resultSet); - } else { - return \think\Collection::make($resultSet); - } + return Config::yaconf($name, $default); } } diff --git a/Server/thinkphp/lang/zh-cn.php b/Server/thinkphp/lang/zh-cn.php index eb7a9142..1e050820 100644 --- a/Server/thinkphp/lang/zh-cn.php +++ b/Server/thinkphp/lang/zh-cn.php @@ -24,6 +24,8 @@ return [ 'dispatch type not support' => '不支持的调度类型', 'method param miss' => '方法参数错误', 'method not exists' => '方法不存在', + 'function not exists' => '函数不存在', + 'file not exists' => '文件不存在', 'module not exists' => '模块不存在', 'controller not exists' => '控制器不存在', 'class not exists' => '类不存在', @@ -32,7 +34,7 @@ return [ 'illegal controller name' => '非法的控制器名称', 'illegal action name' => '非法的操作名称', 'url suffix deny' => '禁止的URL后缀访问', - 'Route Not Found' => '当前访问路由未定义', + 'Route Not Found' => '当前访问路由未定义或不匹配', 'Undefined db type' => '未定义数据库类型', 'variable type error' => '变量类型错误', 'PSR-4 error' => 'PSR-4 规范错误', @@ -48,9 +50,10 @@ return [ 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', 'fields not exists' => '数据表字段不存在', 'where express error' => '查询表达式错误', - 'not support data' => '不支持的数据表达式', + 'order express error' => '排序表达式错误', 'no data to update' => '没有任何数据需要更新', 'miss data to insert' => '缺少需要写入的数据', + 'not support data' => '不支持的数据表达式', 'miss complex primary data' => '缺少复合主键数据', 'miss update condition' => '缺少更新条件', 'model data Not Found' => '模型数据不存在', @@ -67,6 +70,8 @@ return [ 'relation data not exists' => '关联数据不存在', 'relation not support' => '关联不支持', 'chunk not support order' => 'Chunk不支持调用order方法', + 'route pattern error' => '路由变量规则定义错误', + 'route behavior will not support' => '路由行为废弃(使用中间件替代)', 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', // 上传错误信息 @@ -85,8 +90,11 @@ return [ 'filesize not match' => '上传文件大小不符!', 'directory {:path} creation failed' => '目录 {:path} 创建失败!', + 'The middleware must return Response instance' => '中间件方法必须返回Response对象实例', + 'The queue was exhausted, with no response returned' => '中间件队列为空', // Validate Error Message ':attribute require' => ':attribute不能为空', + ':attribute must' => ':attribute必须', ':attribute must be numeric' => ':attribute必须是数字', ':attribute must be integer' => ':attribute必须是整数', ':attribute must be float' => ':attribute必须是浮点数', diff --git a/Server/thinkphp/library/think/App.php b/Server/thinkphp/library/think/App.php index f572b907..692b4227 100644 --- a/Server/thinkphp/library/think/App.php +++ b/Server/thinkphp/library/think/App.php @@ -1,677 +1,979 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; -use think\exception\HttpException; -use think\exception\HttpResponseException; -use think\exception\RouteNotFoundException; - -/** - * App 应用管理 - * @author liu21st - */ -class App -{ - /** - * @var bool 是否初始化过 - */ - protected static $init = false; - - /** - * @var string 当前模块路径 - */ - public static $modulePath; - - /** - * @var bool 应用调试模式 - */ - public static $debug = true; - - /** - * @var string 应用类库命名空间 - */ - public static $namespace = 'app'; - - /** - * @var bool 应用类库后缀 - */ - public static $suffix = false; - - /** - * @var bool 应用路由检测 - */ - protected static $routeCheck; - - /** - * @var bool 严格路由检测 - */ - protected static $routeMust; - - /** - * @var array 请求调度分发 - */ - protected static $dispatch; - - /** - * @var array 额外加载文件 - */ - protected static $file = []; - - /** - * 执行应用程序 - * @access public - * @param Request $request 请求对象 - * @return Response - * @throws Exception - */ - public static function run(Request $request = null) - { - $request = is_null($request) ? Request::instance() : $request; - - try { - $config = self::initCommon(); - - // 模块/控制器绑定 - if (defined('BIND_MODULE')) { - BIND_MODULE && Route::bind(BIND_MODULE); - } elseif ($config['auto_bind_module']) { - // 入口自动绑定 - $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); - if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { - Route::bind($name); - } - } - - $request->filter($config['default_filter']); - - // 默认语言 - Lang::range($config['default_lang']); - // 开启多语言机制 检测当前语言 - $config['lang_switch_on'] && Lang::detect(); - $request->langset(Lang::range()); - - // 加载系统语言包 - Lang::load([ - THINK_PATH . 'lang' . DS . $request->langset() . EXT, - APP_PATH . 'lang' . DS . $request->langset() . EXT, - ]); - - // 监听 app_dispatch - Hook::listen('app_dispatch', self::$dispatch); - // 获取应用调度信息 - $dispatch = self::$dispatch; - - // 未设置调度信息则进行 URL 路由检测 - if (empty($dispatch)) { - $dispatch = self::routeCheck($request, $config); - } - - // 记录当前调度信息 - $request->dispatch($dispatch); - - // 记录路由和请求信息 - if (self::$debug) { - Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); - Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); - Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); - } - - // 监听 app_begin - Hook::listen('app_begin', $dispatch); - - // 请求缓存检查 - $request->cache( - $config['request_cache'], - $config['request_cache_expire'], - $config['request_cache_except'] - ); - - $data = self::exec($dispatch, $config); - } catch (HttpResponseException $exception) { - $data = $exception->getResponse(); - } - - // 清空类的实例化 - Loader::clearInstance(); - - // 输出数据到客户端 - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $type = $request->isAjax() ? - Config::get('default_ajax_return') : - Config::get('default_return_type'); - - $response = Response::create($data, $type); - } else { - $response = Response::create(); - } - - // 监听 app_end - Hook::listen('app_end', $response); - - return $response; - } - - /** - * 初始化应用,并返回配置信息 - * @access public - * @return array - */ - public static function initCommon() - { - if (empty(self::$init)) { - if (defined('APP_NAMESPACE')) { - self::$namespace = APP_NAMESPACE; - } - - Loader::addNamespace(self::$namespace, APP_PATH); - - // 初始化应用 - $config = self::init(); - self::$suffix = $config['class_suffix']; - - // 应用调试模式 - self::$debug = Env::get('app_debug', Config::get('app_debug')); - - if (!self::$debug) { - ini_set('display_errors', 'Off'); - } elseif (!IS_CLI) { - // 重新申请一块比较大的 buffer - if (ob_get_level() > 0) { - $output = ob_get_clean(); - } - - ob_start(); - - if (!empty($output)) { - echo $output; - } - - } - - if (!empty($config['root_namespace'])) { - Loader::addNamespace($config['root_namespace']); - } - - // 加载额外文件 - if (!empty($config['extra_file_list'])) { - foreach ($config['extra_file_list'] as $file) { - $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; - if (is_file($file) && !isset(self::$file[$file])) { - include $file; - self::$file[$file] = true; - } - } - } - - // 设置系统时区 - date_default_timezone_set($config['default_timezone']); - - // 监听 app_init - Hook::listen('app_init'); - - self::$init = true; - } - - return Config::get(); - } - - /** - * 初始化应用或模块 - * @access public - * @param string $module 模块名 - * @return array - */ - private static function init($module = '') - { - // 定位模块目录 - $module = $module ? $module . DS : ''; - - // 加载初始化文件 - if (is_file(APP_PATH . $module . 'init' . EXT)) { - include APP_PATH . $module . 'init' . EXT; - } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { - include RUNTIME_PATH . $module . 'init' . EXT; - } else { - // 加载模块配置 - $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); - - // 读取数据库配置文件 - $filename = CONF_PATH . $module . 'database' . CONF_EXT; - Config::load($filename, 'database'); - - // 读取扩展配置文件 - if (is_dir(CONF_PATH . $module . 'extra')) { - $dir = CONF_PATH . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { - $filename = $dir . DS . $file; - Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - - // 加载应用状态配置 - if ($config['app_status']) { - Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); - } - - // 加载行为扩展文件 - if (is_file(CONF_PATH . $module . 'tags' . EXT)) { - Hook::import(include CONF_PATH . $module . 'tags' . EXT); - } - - // 加载公共文件 - $path = APP_PATH . $module; - if (is_file($path . 'common' . EXT)) { - include $path . 'common' . EXT; - } - - // 加载当前模块语言包 - if ($module) { - Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); - } - } - - return Config::get(); - } - - /** - * 设置当前请求的调度信息 - * @access public - * @param array|string $dispatch 调度信息 - * @param string $type 调度类型 - * @return void - */ - public static function dispatch($dispatch, $type = 'module') - { - self::$dispatch = ['type' => $type, $type => $dispatch]; - } - - /** - * 执行函数或者闭包方法 支持参数调用 - * @access public - * @param string|array|\Closure $function 函数或者闭包 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeFunction($function, $vars = []) - { - $reflect = new \ReflectionFunction($function); - $args = self::bindParams($reflect, $vars); - - // 记录执行信息 - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); - - return $reflect->invokeArgs($args); - } - - /** - * 调用反射执行类的方法 支持参数绑定 - * @access public - * @param string|array $method 方法 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeMethod($method, $vars = []) - { - if (is_array($method)) { - $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); - $reflect = new \ReflectionMethod($class, $method[1]); - } else { - // 静态方法 - $reflect = new \ReflectionMethod($method); - } - - $args = self::bindParams($reflect, $vars); - - self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); - - return $reflect->invokeArgs(isset($class) ? $class : null, $args); - } - - /** - * 调用反射执行类的实例化 支持依赖注入 - * @access public - * @param string $class 类名 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeClass($class, $vars = []) - { - $reflect = new \ReflectionClass($class); - $constructor = $reflect->getConstructor(); - $args = $constructor ? self::bindParams($constructor, $vars) : []; - - return $reflect->newInstanceArgs($args); - } - - /** - * 绑定参数 - * @access private - * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 - * @return array - */ - private static function bindParams($reflect, $vars = []) - { - // 自动获取请求变量 - if (empty($vars)) { - $vars = Config::get('url_param_type') ? - Request::instance()->route() : - Request::instance()->param(); - } - - $args = []; - if ($reflect->getNumberOfParameters() > 0) { - // 判断数组类型 数字数组时按顺序绑定参数 - reset($vars); - $type = key($vars) === 0 ? 1 : 0; - - foreach ($reflect->getParameters() as $param) { - $args[] = self::getParamValue($param, $vars, $type); - } - } - - return $args; - } - - /** - * 获取参数值 - * @access private - * @param \ReflectionParameter $param 参数 - * @param array $vars 变量 - * @param string $type 类别 - * @return array - */ - private static function getParamValue($param, &$vars, $type) - { - $name = $param->getName(); - $class = $param->getClass(); - - if ($class) { - $className = $class->getName(); - $bind = Request::instance()->$name; - - if ($bind instanceof $className) { - $result = $bind; - } else { - if (method_exists($className, 'invoke')) { - $method = new \ReflectionMethod($className, 'invoke'); - - if ($method->isPublic() && $method->isStatic()) { - return $className::invoke(Request::instance()); - } - } - - $result = method_exists($className, 'instance') ? - $className::instance() : - new $className; - } - } elseif (1 == $type && !empty($vars)) { - $result = array_shift($vars); - } elseif (0 == $type && isset($vars[$name])) { - $result = $vars[$name]; - } elseif ($param->isDefaultValueAvailable()) { - $result = $param->getDefaultValue(); - } else { - throw new \InvalidArgumentException('method param miss:' . $name); - } - - return $result; - } - - /** - * 执行调用分发 - * @access protected - * @param array $dispatch 调用信息 - * @param array $config 配置信息 - * @return Response|mixed - * @throws \InvalidArgumentException - */ - protected static function exec($dispatch, $config) - { - switch ($dispatch['type']) { - case 'redirect': // 重定向跳转 - $data = Response::create($dispatch['url'], 'redirect') - ->code($dispatch['status']); - break; - case 'module': // 模块/控制器/操作 - $data = self::module( - $dispatch['module'], - $config, - isset($dispatch['convert']) ? $dispatch['convert'] : null - ); - break; - case 'controller': // 执行控制器操作 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = Loader::action( - $dispatch['controller'], - $vars, - $config['url_controller_layer'], - $config['controller_suffix'] - ); - break; - case 'method': // 回调方法 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = self::invokeMethod($dispatch['method'], $vars); - break; - case 'function': // 闭包 - $data = self::invokeFunction($dispatch['function']); - break; - case 'response': // Response 实例 - $data = $dispatch['response']; - break; - default: - throw new \InvalidArgumentException('dispatch type not support'); - } - - return $data; - } - - /** - * 执行模块 - * @access public - * @param array $result 模块/控制器/操作 - * @param array $config 配置参数 - * @param bool $convert 是否自动转换控制器和操作名 - * @return mixed - * @throws HttpException - */ - public static function module($result, $config, $convert = null) - { - if (is_string($result)) { - $result = explode('/', $result); - } - - $request = Request::instance(); - - if ($config['app_multi_module']) { - // 多模块部署 - $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); - $bind = Route::getBind('module'); - $available = false; - - if ($bind) { - // 绑定模块 - list($bindModule) = explode('/', $bind); - - if (empty($result[0])) { - $module = $bindModule; - $available = true; - } elseif ($module == $bindModule) { - $available = true; - } - } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { - $available = true; - } - - // 模块初始化 - if ($module && $available) { - // 初始化模块 - $request->module($module); - $config = self::init($module); - - // 模块请求缓存检查 - $request->cache( - $config['request_cache'], - $config['request_cache_expire'], - $config['request_cache_except'] - ); - } else { - throw new HttpException(404, 'module not exists:' . $module); - } - } else { - // 单一模块部署 - $module = ''; - $request->module($module); - } - - // 设置默认过滤机制 - $request->filter($config['default_filter']); - - // 当前模块路径 - App::$modulePath = APP_PATH . ($module ? $module . DS : ''); - - // 是否自动转换控制器和操作名 - $convert = is_bool($convert) ? $convert : $config['url_convert']; - - // 获取控制器名 - $controller = strip_tags($result[1] ?: $config['default_controller']); - - if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) { - throw new HttpException(404, 'controller not exists:' . $controller); - } - - $controller = $convert ? strtolower($controller) : $controller; - - // 获取操作名 - $actionName = strip_tags($result[2] ?: $config['default_action']); - if (!empty($config['action_convert'])) { - $actionName = Loader::parseName($actionName, 1); - } else { - $actionName = $convert ? strtolower($actionName) : $actionName; - } - - // 设置当前请求的控制器、操作 - $request->controller(Loader::parseName($controller, 1))->action($actionName); - - // 监听module_init - Hook::listen('module_init', $request); - - try { - $instance = Loader::controller( - $controller, - $config['url_controller_layer'], - $config['controller_suffix'], - $config['empty_controller'] - ); - } catch (ClassNotFoundException $e) { - throw new HttpException(404, 'controller not exists:' . $e->getClass()); - } - - // 获取当前操作名 - $action = $actionName . $config['action_suffix']; - - $vars = []; - if (is_callable([$instance, $action])) { - // 执行操作方法 - $call = [$instance, $action]; - // 严格获取当前操作方法名 - $reflect = new \ReflectionMethod($instance, $action); - $methodName = $reflect->getName(); - $suffix = $config['action_suffix']; - $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; - $request->action($actionName); - - } elseif (is_callable([$instance, '_empty'])) { - // 空操作 - $call = [$instance, '_empty']; - $vars = [$actionName]; - } else { - // 操作不存在 - throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); - } - - Hook::listen('action_begin', $call); - - return self::invokeMethod($call, $vars); - } - - /** - * URL路由检测(根据PATH_INFO) - * @access public - * @param \think\Request $request 请求实例 - * @param array $config 配置信息 - * @return array - * @throws \think\Exception - */ - public static function routeCheck($request, array $config) - { - $path = $request->path(); - $depr = $config['pathinfo_depr']; - $result = false; - - // 路由检测 - $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; - if ($check) { - // 开启路由 - if (is_file(RUNTIME_PATH . 'route.php')) { - // 读取路由缓存 - $rules = include RUNTIME_PATH . 'route.php'; - is_array($rules) && Route::rules($rules); - } else { - $files = $config['route_config_file']; - foreach ($files as $file) { - if (is_file(CONF_PATH . $file . CONF_EXT)) { - // 导入路由配置 - $rules = include CONF_PATH . $file . CONF_EXT; - is_array($rules) && Route::import($rules); - } - } - } - - // 路由检测(根据路由定义返回不同的URL调度) - $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); - $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; - - if ($must && false === $result) { - // 路由无效 - throw new RouteNotFoundException(); - } - } - - // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 - if (false === $result) { - $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); - } - - return $result; - } - - /** - * 设置应用的路由检测机制 - * @access public - * @param bool $route 是否需要检测路由 - * @param bool $must 是否强制检测路由 - * @return void - */ - public static function route($route, $must = false) - { - self::$routeCheck = $route; - self::$routeMust = $must; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpResponseException; +use think\route\Dispatch; + +/** + * App 应用管理 + */ +class App extends Container +{ + const VERSION = '5.1.41 LTS'; + + /** + * 当前模块路径 + * @var string + */ + protected $modulePath; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = true; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用类库后缀 + * @var bool + */ + protected $suffix = false; + + /** + * 严格路由检测 + * @var bool + */ + protected $routeMust; + + /** + * 应用类库目录 + * @var string + */ + protected $appPath; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath; + + /** + * 运行时目录 + * @var string + */ + protected $runtimePath; + + /** + * 配置目录 + * @var string + */ + protected $configPath; + + /** + * 路由目录 + * @var string + */ + protected $routePath; + + /** + * 配置后缀 + * @var string + */ + protected $configExt; + + /** + * 应用调度实例 + * @var Dispatch + */ + protected $dispatch; + + /** + * 绑定模块(控制器) + * @var string + */ + protected $bindModule; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + public function __construct($appPath = '') + { + $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $this->path($appPath); + } + + /** + * 绑定模块或者控制器 + * @access public + * @param string $bind + * @return $this + */ + public function bind($bind) + { + $this->bindModule = $bind; + return $this; + } + + /** + * 设置应用类库目录 + * @access public + * @param string $path 路径 + * @return $this + */ + public function path($path) + { + $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath(); + + return $this; + } + + /** + * 初始化应用 + * @access public + * @return void + */ + public function initialize() + { + if ($this->initialized) { + return; + } + + $this->initialized = true; + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; + $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + + static::setInstance($this); + + $this->instance('app', $this); + + // 加载环境变量配置文件 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + + $this->configExt = $this->env->get('config_ext', '.php'); + + // 加载惯例配置文件 + $this->config->set(include $this->thinkPath . 'convention.php'); + + // 设置路径环境变量 + $this->env->set([ + 'think_path' => $this->thinkPath, + 'root_path' => $this->rootPath, + 'app_path' => $this->appPath, + 'config_path' => $this->configPath, + 'route_path' => $this->routePath, + 'runtime_path' => $this->runtimePath, + 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, + 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, + ]); + + $this->namespace = $this->env->get('app_namespace', $this->namespace); + $this->env->set('app_namespace', $this->namespace); + + // 注册应用命名空间 + Loader::addNamespace($this->namespace, $this->appPath); + + // 初始化应用 + $this->init(); + + // 开启类名后缀 + $this->suffix = $this->config('app.class_suffix'); + + // 应用调试模式 + $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->appDebug); + + if (!$this->appDebug) { + ini_set('display_errors', 'Off'); + } elseif (PHP_SAPI != 'cli') { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + + // 注册异常处理类 + if ($this->config('app.exception_handle')) { + Error::setExceptionHandler($this->config('app.exception_handle')); + } + + // 注册根命名空间 + if (!empty($this->config('app.root_namespace'))) { + Loader::addNamespace($this->config('app.root_namespace')); + } + + // 加载composer autofile文件 + Loader::loadComposerAutoloadFiles(); + + // 注册类库别名 + Loader::addClassAlias($this->config->pull('alias')); + + // 数据库配置初始化 + Db::init($this->config->pull('database')); + + // 设置系统时区 + date_default_timezone_set($this->config('app.default_timezone')); + + // 读取语言包 + $this->loadLangPack(); + + // 路由初始化 + $this->routeInit(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return void + */ + public function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DIRECTORY_SEPARATOR : ''; + $path = $this->appPath . $module; + + // 加载初始化文件 + if (is_file($path . 'init.php')) { + include $path . 'init.php'; + } elseif (is_file($this->runtimePath . $module . 'init.php')) { + include $this->runtimePath . $module . 'init.php'; + } else { + // 加载行为扩展文件 + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $this->hook->import($tags); + } + } + + // 加载公共文件 + if (is_file($path . 'common.php')) { + include_once $path . 'common.php'; + } + + if ('' == $module) { + // 加载系统助手函数 + include $this->thinkPath . 'helper.php'; + } + + // 加载中间件 + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $this->middleware->import($middleware); + } + } + + // 注册服务的容器对象实例 + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $this->bindTo($provider); + } + } + + // 自动读取配置文件 + if (is_dir($path . 'config')) { + $dir = $path . 'config' . DIRECTORY_SEPARATOR; + } elseif (is_dir($this->configPath . $module)) { + $dir = $this->configPath . $module; + } + + $files = isset($dir) ? scandir($dir) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { + $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + $this->setModulePath($path); + + if ($module) { + // 对容器中的对象实例进行配置更新 + $this->containerConfigUpdate($module); + } + } + + protected function containerConfigUpdate($module) + { + $config = $this->config->get(); + + // 注册异常处理类 + if ($config['app']['exception_handle']) { + Error::setExceptionHandler($config['app']['exception_handle']); + } + + Db::init($config['database']); + $this->middleware->setConfig($config['middleware']); + $this->route->setConfig($config['app']); + $this->request->init($config['app']); + $this->cookie->init($config['cookie']); + $this->view->init($config['template']); + $this->log->init($config['log']); + $this->session->setConfig($config['session']); + $this->debug->setConfig($config['trace']); + $this->cache->init($config['cache'], true); + + // 加载当前模块语言包 + $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); + + // 模块请求缓存检查 + $this->checkRequestCache( + $config['app']['request_cache'], + $config['app']['request_cache_expire'], + $config['app']['request_cache_except'] + ); + } + + /** + * 执行应用程序 + * @access public + * @return Response + * @throws Exception + */ + public function run() + { + try { + // 初始化应用 + $this->initialize(); + + // 监听app_init + $this->hook->listen('app_init'); + + if ($this->bindModule) { + // 模块/控制器绑定 + $this->route->bind($this->bindModule); + } elseif ($this->config('app.auto_bind_module')) { + // 入口自动绑定 + $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir($this->appPath . $name)) { + $this->route->bind($name); + } + } + + // 监听app_dispatch + $this->hook->listen('app_dispatch'); + + $dispatch = $this->dispatch; + + if (empty($dispatch)) { + // 路由检测 + $dispatch = $this->routeCheck()->init(); + } + + // 记录当前调度信息 + $this->request->dispatch($dispatch); + + // 记录路由和请求信息 + if ($this->appDebug) { + $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); + $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); + $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); + } + + // 监听app_begin + $this->hook->listen('app_begin'); + + // 请求缓存检查 + $this->checkRequestCache( + $this->config('request_cache'), + $this->config('request_cache_expire'), + $this->config('request_cache_except') + ); + + $data = null; + } catch (HttpResponseException $exception) { + $dispatch = null; + $data = $exception->getResponse(); + } + + $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { + return is_null($data) ? $dispatch->run() : $data; + }); + + $response = $this->middleware->dispatch($this->request); + + // 监听app_end + $this->hook->listen('app_end', $response); + + return $response; + } + + protected function getRouteCacheKey() + { + if ($this->config->get('route_check_cache_key')) { + $closure = $this->config->get('route_check_cache_key'); + $routeKey = $closure($this->request); + } else { + $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); + } + + return $routeKey; + } + + protected function loadLangPack() + { + // 读取默认语言 + $this->lang->range($this->config('app.default_lang')); + + if ($this->config('app.lang_switch_on')) { + // 开启多语言机制 检测当前语言 + $this->lang->detect(); + } + + $this->request->setLangset($this->lang->range()); + + // 加载系统语言包 + $this->lang->load([ + $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + ]); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function checkRequestCache($key, $expire = null, $except = [], $tag = null) + { + $cache = $this->request->cache($key, $expire, $except, $tag); + + if ($cache) { + $this->setResponseCache($cache); + } + } + + public function setResponseCache($cache) + { + list($key, $expire, $tag) = $cache; + + if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { + // 读取缓存 + $response = Response::create()->code(304); + throw new HttpResponseException($response); + } elseif ($this->cache->has($key)) { + list($content, $header) = $this->cache->get($key); + + $response = Response::create($content)->header($header); + throw new HttpResponseException($response); + } + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param Dispatch $dispatch 调度信息 + * @return $this + */ + public function dispatch(Dispatch $dispatch) + { + $this->dispatch = $dispatch; + return $this; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public function log($msg, $type = 'info') + { + $this->appDebug && $this->log->record($msg, $type); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 .号分割) + * @return mixed + */ + public function config($name = '') + { + return $this->config->get($name); + } + + /** + * 路由初始化 导入路由定义规则 + * @access public + * @return void + */ + public function routeInit() + { + // 路由检测 + if (is_dir($this->routePath)) { + $files = glob($this->routePath . '*.php'); + foreach ($files as $file) { + $rules = include $file; + if (is_array($rules)) { + $this->route->import($rules); + } + } + } + + if ($this->route->config('route_annotation')) { + // 自动生成路由定义 + if ($this->appDebug) { + $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); + $this->build->buildRoute($suffix); + } + + $filename = $this->runtimePath . 'build_route.php'; + + if (is_file($filename)) { + include $filename; + } + } + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @return Dispatch + */ + public function routeCheck() + { + // 检测路由缓存 + if (!$this->appDebug && $this->config->get('route_check_cache')) { + $routeKey = $this->getRouteCacheKey(); + $option = $this->config->get('route_cache_option'); + + if ($option && $this->cache->connect($option)->has($routeKey)) { + return $this->cache->connect($option)->get($routeKey); + } elseif ($this->cache->has($routeKey)) { + return $this->cache->get($routeKey); + } + } + + // 获取应用调度信息 + $path = $this->request->path(); + + // 是否强制路由模式 + $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); + + // 路由检测 返回一个Dispatch对象 + $dispatch = $this->route->check($path, $must); + + if (!empty($routeKey)) { + try { + if ($option) { + $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch); + } else { + $this->cache->tag('route_cache')->set($routeKey, $dispatch); + } + } catch (\Exception $e) { + // 存在闭包的时候缓存无效 + } + } + + return $dispatch; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $must 是否强制检测路由 + * @return $this + */ + public function routeMust($must = false) + { + $this->routeMust = $must; + return $this; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected function parseModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $class = $name; + $module = $this->request->module(); + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = $this->request->module(); + } + + $class = $this->parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 实例化应用类库 + * @access public + * @param string $name 类名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public function create($name, $layer, $appendSuffix = false, $common = 'common') + { + $guid = $name . $layer; + + if ($this->__isset($guid)) { + return $this->__get($guid); + } + + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $object = $this->__get($class); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + if (class_exists($class)) { + $object = $this->__get($class); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + $this->__set($guid, $class); + + return $object; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Model + * @throws ClassNotFoundException + */ + public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return $this->make($class, true); + } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { + return $this->make($emptyClass, true); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Validate + * @throws ClassNotFoundException + */ + public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: $this->config('default_validate'); + + if (empty($name)) { + return new Validate; + } + + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 数据库初始化 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Query + */ + public function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + * @throws ClassNotFoundException + */ + public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); + $class = $this->controller($module, $layer, $appendSuffix); + + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix + * @return string + */ + public function parseClass($module, $layer, $name, $appendSuffix = false) + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version() + { + return static::VERSION; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug() + { + return $this->appDebug; + } + + /** + * 获取模块路径 + * @access public + * @return string + */ + public function getModulePath() + { + return $this->modulePath; + } + + /** + * 设置模块路径 + * @access public + * @param string $path 路径 + * @return void + */ + public function setModulePath($path) + { + $this->modulePath = $path; + $this->env->set('module_path', $path); + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath() + { + return $this->rootPath; + } + + /** + * 获取应用类库目录 + * @access public + * @return string + */ + public function getAppPath() + { + if (is_null($this->appPath)) { + $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; + } + + return $this->appPath; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath() + { + return $this->runtimePath; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath() + { + return $this->thinkPath; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath() + { + return $this->routePath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath() + { + return $this->configPath; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt() + { + return $this->configExt; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * 设置应用类库命名空间 + * @access public + * @param string $namespace 命名空间名称 + * @return $this + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 是否启用类库后缀 + * @access public + * @return bool + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime() + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem() + { + return $this->beginMem; + } + +} diff --git a/Server/thinkphp/library/think/Build.php b/Server/thinkphp/library/think/Build.php index de7c3275..7a531d74 100644 --- a/Server/thinkphp/library/think/Build.php +++ b/Server/thinkphp/library/think/Build.php @@ -14,44 +14,57 @@ namespace think; class Build { /** - * 根据传入的 build 资料创建目录和文件 - * @access public - * @param array $build build 列表 - * @param string $namespace 应用类库命名空间 - * @param bool $suffix 类库后缀 - * @return void - * @throws Exception + * 应用对象 + * @var App */ - public static function run(array $build = [], $namespace = 'app', $suffix = false) + protected $app; + + /** + * 应用目录 + * @var string + */ + protected $basePath; + + public function __construct(App $app) + { + $this->app = $app; + $this->basePath = $this->app->getAppPath(); + } + + /** + * 根据传入的build资料创建目录和文件 + * @access public + * @param array $build build列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + */ + public function run(array $build = [], $namespace = 'app', $suffix = false) { // 锁定 - $lock = APP_PATH . 'build.lock'; + $lockfile = $this->basePath . 'build.lock'; - // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了 - if (!is_writable($lock)) { - if (!touch($lock)) { - throw new Exception( - '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!
    请手动生成项目目录~', - 10006 - ); - } - - foreach ($build as $module => $list) { - if ('__dir__' == $module) { - // 创建目录列表 - self::buildDir($list); - } elseif ('__file__' == $module) { - // 创建文件列表 - self::buildFile($list); - } else { - // 创建模块 - self::module($module, $list, $namespace, $suffix); - } - } - - // 解除锁定 - unlink($lock); + if (is_writable($lockfile)) { + return; + } elseif (!touch($lockfile)) { + throw new Exception('应用目录[' . $this->basePath . ']不可写,目录无法自动生成!
    请手动生成项目目录~', 10006); } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + $this->buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + $this->buildFile($list); + } else { + // 创建模块 + $this->module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lockfile); } /** @@ -60,11 +73,10 @@ class Build * @param array $list 目录列表 * @return void */ - protected static function buildDir($list) + protected function buildDir($list) { foreach ($list as $dir) { - // 目录不存在则创建目录 - !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true); + $this->checkDirBuild($this->basePath . $dir); } } @@ -74,20 +86,16 @@ class Build * @param array $list 文件列表 * @return void */ - protected static function buildFile($list) + protected function buildFile($list) { foreach ($list as $file) { - // 先创建目录 - if (!is_dir(APP_PATH . dirname($file))) { - mkdir(APP_PATH . dirname($file), 0755, true); + if (!is_dir($this->basePath . dirname($file))) { + // 创建目录 + mkdir($this->basePath . dirname($file), 0755, true); } - // 再创建文件 - if (!is_file(APP_PATH . $file)) { - file_put_contents( - APP_PATH . $file, - 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "basePath . $file)) { + file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "basePath . $module)) { + // 创建模块目录 + mkdir($this->basePath . $module); + } + + if (basename($this->app->getRuntimePath()) != $module) { + // 创建配置文件和公共文件 + $this->buildCommon($module); + // 创建模块的默认页面 + $this->buildHello($module, $namespace, $suffix); } - // 未指定文件和目录,则创建默认的模块目录和文件 if (empty($list)) { + // 创建默认的模块目录和文件 $list = [ - '__file__' => ['config.php', 'common.php'], - '__dir__' => ['controller', 'model', 'view'], + '__file__' => ['common.php'], + '__dir__' => ['controller', 'model', 'view', 'config'], ]; } // 创建子目录和文件 foreach ($list as $path => $file) { - $modulePath = APP_PATH . $module . DS; - + $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR; if ('__dir__' == $path) { // 生成子目录 foreach ($file as $dir) { - self::checkDirBuild($modulePath . $dir); + $this->checkDirBuild($modulePath . $dir); } } elseif ('__file__' == $path) { // 生成(空白)文件 foreach ($file as $name) { if (!is_file($modulePath . $name)) { - file_put_contents( - $modulePath . $name, - 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "checkDirBuild(dirname($filename)); $content = ''; break; default: @@ -174,30 +180,200 @@ class Build } } + /** + * 根据注释自动生成路由规则 + * @access public + * @param bool $suffix 类库后缀 + * @param string $layer 控制器层目录名 + * @return string + */ + public function buildRoute($suffix = false, $layer = '') + { + $namespace = $this->app->getNameSpace(); + $content = 'app->config('app.url_controller_layer'); + } + + if ($this->app->config('app.app_multi_module')) { + $modules = glob($this->basePath . '*', GLOB_ONLYDIR); + + foreach ($modules as $module) { + $module = basename($module); + + if (in_array($module, $this->app->config('app.deny_module_list'))) { + continue; + } + + $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR; + $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer); + } + } else { + $path = $this->basePath . $layer . DIRECTORY_SEPARATOR; + $content .= $this->buildDirRoute($path, $namespace, '', $suffix, $layer); + } + + $filename = $this->app->getRuntimePath() . 'build_route.php'; + file_put_contents($filename, $content); + + return $filename; + } + + /** + * 生成子目录控制器类的路由规则 + * @access protected + * @param string $path 控制器目录 + * @param string $namespace 应用命名空间 + * @param string $module 模块 + * @param bool $suffix 类库后缀 + * @param string $layer 控制器层目录名 + * @return string + */ + protected function buildDirRoute($path, $namespace, $module, $suffix, $layer) + { + $content = ''; + $controllers = glob($path . '*.php'); + + foreach ($controllers as $controller) { + $controller = basename($controller, '.php'); + + $class = new \ReflectionClass($namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $controller); + + if (strpos($layer, '\\')) { + // 多级控制器 + $level = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11)); + $controller = $level . '.' . $controller; + $length = strlen(strstr($layer, '\\', true)); + } else { + $length = strlen($layer); + } + + if ($suffix) { + $controller = substr($controller, 0, -$length); + } + + $content .= $this->getControllerRoute($class, $module, $controller); + } + + $subDir = glob($path . '*', GLOB_ONLYDIR); + + foreach ($subDir as $dir) { + $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir)); + } + + return $content; + } + + /** + * 生成控制器类的路由规则 + * @access protected + * @param string $class 控制器完整类名 + * @param string $module 模块名 + * @param string $controller 控制器名 + * @return string + */ + protected function getControllerRoute($class, $module, $controller) + { + $content = ''; + $comment = $class->getDocComment(); + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $route = ($module ? $module . '/' : '') . $controller; + $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } elseif (false !== strpos($comment, '@alias(')) { + $comment = $this->parseRouteComment($comment, '@alias('); + $route = ($module ? $module . '/' : '') . $controller; + $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } + + $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); + + foreach ($methods as $method) { + $comment = $this->getMethodRouteComment($module, $controller, $method); + if ($comment) { + $content .= PHP_EOL . $comment; + } + } + + return $content; + } + + /** + * 解析路由注释 + * @access protected + * @param string $comment + * @param string $tag + * @return string + */ + protected function parseRouteComment($comment, $tag = '@route(') + { + $comment = substr($comment, 3, -2); + $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1)); + $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment); + + if (count($comment) > 1) { + $key = array_search('', $comment); + $comment = array_slice($comment, 0, false === $key ? 1 : $key); + } + + $comment = implode(PHP_EOL . "\t", $comment) . ';'; + + if (strpos($comment, '{')) { + $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) { + return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0]; + }, $comment); + } + return $comment; + } + + /** + * 获取方法的路由注释 + * @access protected + * @param string $module 模块 + * @param string $controller 控制器名 + * @param \ReflectMethod $reflectMethod + * @return string|void + */ + protected function getMethodRouteComment($module, $controller, $reflectMethod) + { + $comment = $reflectMethod->getDocComment(); + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $action = $reflectMethod->getName(); + + if ($suffix = $this->app->config('app.action_suffix')) { + $action = substr($action, 0, -strlen($suffix)); + } + + $route = ($module ? $module . '/' : '') . $controller . '/' . $action; + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment); + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment); + + return $comment; + } + } + /** * 创建模块的欢迎页面 * @access protected - * @param string $module 模块名 + * @param string $module 模块名 * @param string $namespace 应用类库命名空间 - * @param bool $suffix 类库后缀 + * @param bool $suffix 类库后缀 * @return void */ - protected static function buildHello($module, $namespace, $suffix = false) + protected function buildHello($module, $namespace, $suffix = false) { - $filename = APP_PATH . ($module ? $module . DS : '') . - 'controller' . DS . 'Index' . - ($suffix ? 'Controller' : '') . EXT; - + $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . ($suffix ? 'Controller' : '') . '.php'; if (!is_file($filename)) { - $module = $module ? $module . '\\' : ''; - $suffix = $suffix ? 'Controller' : ''; - $content = str_replace( - ['{$app}', '{$module}', '{layer}', '{$suffix}'], - [$namespace, $module, 'controller', $suffix], - file_get_contents(THINK_PATH . 'tpl' . DS . 'default_index.tpl') - ); + $content = file_get_contents($this->app->getThinkPath() . 'tpl' . DIRECTORY_SEPARATOR . 'default_index.tpl'); + $content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content); + $this->checkDirBuild(dirname($filename)); - self::checkDirBuild(dirname($filename)); file_put_contents($filename, $content); } } @@ -208,18 +384,20 @@ class Build * @param string $module 模块名 * @return void */ - protected static function buildCommon($module) + protected function buildCommon($module) { - $config = CONF_PATH . ($module ? $module . DS : '') . 'config.php'; + $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php'; + $this->checkDirBuild(dirname($filename)); - self::checkDirBuild(dirname($config)); - - if (!is_file($config)) { - file_put_contents($config, "basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php'; + + if (!is_file($filename)) { + file_put_contents($filename, "config = $config; + $this->init($config); + } /** - * @var object 操作句柄 - */ - public static $handler; - - /** - * 连接缓存驱动 + * 连接缓存 * @access public - * @param array $options 配置数组 - * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 * @return Driver */ - public static function connect(array $options = [], $name = false) + public function connect(array $options = [], $name = false) { - $type = !empty($options['type']) ? $options['type'] : 'File'; - if (false === $name) { $name = md5(serialize($options)); } - if (true === $name || !isset(self::$instance[$name])) { - $class = false === strpos($type, '\\') ? - '\\think\\cache\\driver\\' . ucwords($type) : - $type; - - // 记录初始化信息 - App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); + if (true === $name || !isset($this->instance[$name])) { + $type = !empty($options['type']) ? $options['type'] : 'File'; if (true === $name) { - return new $class($options); + $name = md5(serialize($options)); } - self::$instance[$name] = new $class($options); + $this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options); } - return self::$instance[$name]; + return $this->instance[$name]; } /** * 自动初始化缓存 * @access public - * @param array $options 配置数组 + * @param array $options 配置数组 + * @param bool $force 强制更新 * @return Driver */ - public static function init(array $options = []) + public function init(array $options = [], $force = false) { - if (is_null(self::$handler)) { - if (empty($options) && 'complex' == Config::get('cache.type')) { - $default = Config::get('cache.default'); - // 获取默认缓存配置,并连接 - $options = Config::get('cache.' . $default['type']) ?: $default; - } elseif (empty($options)) { - $options = Config::get('cache'); + if (is_null($this->handler) || $force) { + + if ('complex' == $options['type']) { + $default = $options['default']; + $options = isset($options[$default['type']]) ? $options[$default['type']] : $default; } - self::$handler = self::connect($options); + $this->handler = $this->connect($options); } - return self::$handler; + return $this->handler; + } + + public static function __make(Config $config) + { + return new static($config->pull('cache')); + } + + public function getConfig() + { + return $this->config; + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); } /** @@ -97,151 +116,18 @@ class Cache * @param string $name 缓存标识 * @return Driver */ - public static function store($name = '') + public function store($name = '') { - if ('' !== $name && 'complex' == Config::get('cache.type')) { - return self::connect(Config::get('cache.' . $name), strtolower($name)); + if ('' !== $name && 'complex' == $this->config['type']) { + return $this->connect($this->config[$name], strtolower($name)); } - return self::init(); + return $this->init(); } - /** - * 判断缓存是否存在 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public static function has($name) + public function __call($method, $args) { - self::$readTimes++; - - return self::init()->has($name); - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存标识 - * @param mixed $default 默认值 - * @return mixed - */ - public static function get($name, $default = false) - { - self::$readTimes++; - - return self::init()->get($name, $default); - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存标识 - * @param mixed $value 存储数据 - * @param int|null $expire 有效时间 0为永久 - * @return boolean - */ - public static function set($name, $value, $expire = null) - { - self::$writeTimes++; - - return self::init()->set($name, $value, $expire); - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function inc($name, $step = 1) - { - self::$writeTimes++; - - return self::init()->inc($name, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function dec($name, $step = 1) - { - self::$writeTimes++; - - return self::init()->dec($name, $step); - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存标识 - * @return boolean - */ - public static function rm($name) - { - self::$writeTimes++; - - return self::init()->rm($name); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public static function clear($tag = null) - { - self::$writeTimes++; - - return self::init()->clear($tag); - } - - /** - * 读取缓存并删除 - * @access public - * @param string $name 缓存变量名 - * @return mixed - */ - public static function pull($name) - { - self::$readTimes++; - self::$writeTimes++; - - return self::init()->pull($name); - } - - /** - * 如果不存在则写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 - * @return mixed - */ - public static function remember($name, $value, $expire = null) - { - self::$readTimes++; - - return self::init()->remember($name, $value, $expire); - } - - /** - * 缓存标签 - * @access public - * @param string $name 标签名 - * @param string|array $keys 缓存标识 - * @param bool $overlay 是否覆盖 - * @return Driver - */ - public static function tag($name, $keys = null, $overlay = false) - { - return self::init()->tag($name, $keys, $overlay); + return call_user_func_array([$this->init(), $method], $args); } } diff --git a/Server/thinkphp/library/think/Collection.php b/Server/thinkphp/library/think/Collection.php index f872476f..d7454ec5 100644 --- a/Server/thinkphp/library/think/Collection.php +++ b/Server/thinkphp/library/think/Collection.php @@ -20,33 +20,23 @@ use JsonSerializable; class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable { /** - * @var array 数据 + * 数据集数据 + * @var array */ protected $items = []; - /** - * Collection constructor. - * @access public - * @param array $items 数据 - */ public function __construct($items = []) { $this->items = $this->convertToArray($items); } - /** - * 创建 Collection 实例 - * @access public - * @param array $items 数据 - * @return static - */ public static function make($items = []) { return new static($items); } /** - * 判断数据是否为空 + * 是否为空 * @access public * @return bool */ @@ -55,32 +45,33 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return empty($this->items); } - /** - * 将数据转成数组 - * @access public - * @return array - */ public function toArray() { return array_map(function ($value) { - return ($value instanceof Model || $value instanceof self) ? - $value->toArray() : - $value; + return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value; }, $this->items); } - /** - * 获取全部的数据 - * @access public - * @return array - */ public function all() { return $this->items; } + /** + * 合并数组 + * + * @access public + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + /** * 交换数组中的键和值 + * * @access public * @return static */ @@ -90,60 +81,112 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 返回数组中所有的键名组成的新 Collection 实例 + * 按指定键整理数据 + * * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, &$indexKey = null) + { + if ($items instanceof self || $items instanceof Paginator) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 * @return static */ + public function diff($items, $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数组,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } + + /** + * 返回数组中所有的键名 + * + * @access public + * @return array + */ public function keys() { - return new static(array_keys($this->items)); + $current = current($this->items); + + if (is_scalar($current)) { + $array = $this->items; + } elseif (is_array($current)) { + $array = $current; + } else { + $array = $current->toArray(); + } + + return array_keys($array); } /** - * 返回数组中所有的值组成的新 Collection 实例 - * @access public - * @return static - */ - public function values() - { - return new static(array_values($this->items)); - } - - /** - * 合并数组并返回一个新的 Collection 实例 - * @access public - * @param mixed $items 新的数据 - * @return static - */ - public function merge($items) - { - return new static(array_merge($this->items, $this->convertToArray($items))); - } - - /** - * 比较数组,返回差集生成的新 Collection 实例 - * @access public - * @param mixed $items 做比较的数据 - * @return static - */ - public function diff($items) - { - return new static(array_diff($this->items, $this->convertToArray($items))); - } - - /** - * 比较数组,返回交集组成的 Collection 新实例 - * @access public - * @param mixed $items 比较数据 - * @return static - */ - public function intersect($items) - { - return new static(array_intersect($this->items, $this->convertToArray($items))); - } - - /** - * 返回并删除数据中的的最后一个元素(出栈) + * 删除数组的最后一个元素(出栈) + * * @access public * @return mixed */ @@ -153,7 +196,32 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 返回并删除数据中首个元素 + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * * @access public * @return mixed */ @@ -162,27 +230,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return array_shift($this->items); } - /** - * 在数组开头插入一个元素 - * @access public - * @param mixed $value 值 - * @param mixed $key 键名 - * @return void - */ - public function unshift($value, $key = null) - { - if (is_null($key)) { - array_unshift($this->items, $value); - } else { - $this->items = [$key => $value] + $this->items; - } - } - /** * 在数组结尾插入一个元素 * @access public - * @param mixed $value 值 - * @param mixed $key 键名 + * @param mixed $value + * @param mixed $key * @return void */ public function push($value, $key = null) @@ -195,32 +247,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 通过使用用户自定义函数,以字符串返回数组 + * 把一个数组分割为新的数组块. + * * @access public - * @param callable $callback 回调函数 - * @param mixed $initial 初始值 - * @return mixed - */ - public function reduce(callable $callback, $initial = null) - { - return array_reduce($this->items, $callback, $initial); - } - - /** - * 以相反的顺序创建一个新的 Collection 实例 - * @access public - * @return static - */ - public function reverse() - { - return new static(array_reverse($this->items)); - } - - /** - * 把数据分割为新的数组块 - * @access public - * @param int $size 分隔长度 - * @param bool $preserveKeys 是否保持原数据索引 + * @param int $size + * @param bool $preserveKeys * @return static */ public function chunk($size, $preserveKeys = false) @@ -235,9 +266,26 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 给数据中的每个元素执行回调 + * 在数组开头插入一个元素 * @access public - * @param callable $callback 回调函数 + * @param mixed $value + * @param mixed $key + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback * @return $this */ public function each(callable $callback) @@ -247,9 +295,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria if (false === $result) { break; - } - - if (!is_object($item)) { + } elseif (!is_object($item)) { $this->items[$key] = $result; } } @@ -258,78 +304,151 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } /** - * 用回调函数过滤数据中的元素 + * 用回调函数处理数组中的元素 * @access public - * @param callable|null $callback 回调函数 + * @param callable|null $callback + * @return static + */ + public function map(callable $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * 用回调函数过滤数组中的元素 + * @access public + * @param callable|null $callback * @return static */ public function filter(callable $callback = null) { - return new static(array_filter($this->items, $callback ?: null)); + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * 根据字段条件过滤数组中的元素 + * @access public + * @param string $field 字段名 + * @param mixed $operator 操作符 + * @param mixed $value 数据 + * @return static + */ + public function where($field, $operator, $value = null) + { + if (is_null($value)) { + $value = $operator; + $operator = '='; + } + + return $this->filter(function ($data) use ($field, $operator, $value) { + if (strpos($field, '.')) { + list($field, $relation) = explode('.', $field); + + $result = isset($data[$field][$relation]) ? $data[$field][$relation] : null; + } else { + $result = isset($data[$field]) ? $data[$field] : null; + } + + switch (strtolower($operator)) { + case '===': + return $result === $value; + case '!==': + return $result !== $value; + case '!=': + case '<>': + return $result != $value; + case '>': + return $result > $value; + case '>=': + return $result >= $value; + case '<': + return $result < $value; + case '<=': + return $result <= $value; + case 'like': + return is_string($result) && false !== strpos($result, $value); + case 'not like': + return is_string($result) && false === strpos($result, $value); + case 'in': + return is_scalar($result) && in_array($result, $value, true); + case 'not in': + return is_scalar($result) && !in_array($result, $value, true); + case 'between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result >= $min && $result <= $max; + case 'not between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result > $max || $result < $min; + case '==': + case '=': + default: + return $result == $value; + } + }); } /** * 返回数据中指定的一列 * @access public * @param mixed $columnKey 键名 - * @param null $indexKey 作为索引值的列 + * @param mixed $indexKey 作为索引值的列 * @return array */ public function column($columnKey, $indexKey = null) { - if (function_exists('array_column')) { - return array_column($this->items, $columnKey, $indexKey); - } - - $result = []; - foreach ($this->items as $row) { - $key = $value = null; - $keySet = $valueSet = false; - - if (null !== $indexKey && array_key_exists($indexKey, $row)) { - $key = (string) $row[$indexKey]; - $keySet = true; - } - - if (null === $columnKey) { - $valueSet = true; - $value = $row; - } elseif (is_array($row) && array_key_exists($columnKey, $row)) { - $valueSet = true; - $value = $row[$columnKey]; - } - - if ($valueSet) { - if ($keySet) { - $result[$key] = $value; - } else { - $result[] = $value; - } - } - } - - return $result; + return array_column($this->toArray(), $columnKey, $indexKey); } /** - * 对数据排序,并返回排序后的数据组成的新 Collection 实例 + * 对数组排序 + * * @access public - * @param callable|null $callback 回调函数 + * @param callable|null $callback * @return static */ public function sort(callable $callback = null) { - $items = $this->items; + $items = $this->items; + $callback = $callback ?: function ($a, $b) { return $a == $b ? 0 : (($a < $b) ? -1 : 1); + }; uasort($items, $callback); + return new static($items); } /** - * 将数据打乱后组成新的 Collection 实例 + * 指定字段排序 + * @access public + * @param string $field 排序字段 + * @param string $order 排序 + * @param bool $intSort 是否为数字排序 + * @return $this + */ + public function order($field, $order = null, $intSort = true) + { + return $this->sort(function ($a, $b) use ($field, $order, $intSort) { + $fieldA = isset($a[$field]) ? $a[$field] : null; + $fieldB = isset($b[$field]) ? $b[$field] : null; + + if ($intSort) { + return 'desc' == strtolower($order) ? $fieldB >= $fieldA : $fieldA >= $fieldB; + } else { + return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB); + } + }); + } + + /** + * 将数组打乱 + * * @access public * @return static */ @@ -338,15 +457,17 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria $items = $this->items; shuffle($items); + return new static($items); } /** - * 截取数据并返回新的 Collection 实例 + * 截取数组 + * * @access public - * @param int $offset 起始位置 - * @param int $length 截取长度 - * @param bool $preserveKeys 是否保持原先的键名 + * @param int $offset + * @param int $length + * @param bool $preserveKeys * @return static */ public function slice($offset, $length = null, $preserveKeys = false) @@ -354,35 +475,17 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return new static(array_slice($this->items, $offset, $length, $preserveKeys)); } - /** - * 指定的键是否存在 - * @access public - * @param mixed $offset 键名 - * @return bool - */ + // ArrayAccess public function offsetExists($offset) { return array_key_exists($offset, $this->items); } - /** - * 获取指定键对应的值 - * @access public - * @param mixed $offset 键名 - * @return mixed - */ public function offsetGet($offset) { return $this->items[$offset]; } - /** - * 设置键值 - * @access public - * @param mixed $offset 键名 - * @param mixed $value 值 - * @return void - */ public function offsetSet($offset, $value) { if (is_null($offset)) { @@ -392,51 +495,33 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria } } - /** - * 删除指定键值 - * @access public - * @param mixed $offset 键名 - * @return void - */ public function offsetUnset($offset) { unset($this->items[$offset]); } - /** - * 统计数据的个数 - * @access public - * @return int - */ + //Countable public function count() { return count($this->items); } - /** - * 获取数据的迭代器 - * @access public - * @return ArrayIterator - */ + //IteratorAggregate public function getIterator() { return new ArrayIterator($this->items); } - /** - * 将数据反序列化成数组 - * @access public - * @return array - */ + //JsonSerializable public function jsonSerialize() { return $this->toArray(); } /** - * 转换当前数据集为 JSON 字符串 + * 转换当前数据集为JSON字符串 * @access public - * @param integer $options json 参数 + * @param integer $options json参数 * @return string */ public function toJson($options = JSON_UNESCAPED_UNICODE) @@ -444,24 +529,24 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria return json_encode($this->toArray(), $options); } - /** - * 将数据转换成字符串 - * @access public - * @return string - */ public function __toString() { return $this->toJson(); } /** - * 将数据转换成数组 - * @access protected - * @param mixed $items 数据 + * 转换成数组 + * + * @access public + * @param mixed $items * @return array */ protected function convertToArray($items) { - return $items instanceof self ? $items->all() : (array) $items; + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; } } diff --git a/Server/thinkphp/library/think/Config.php b/Server/thinkphp/library/think/Config.php index 8fa668d1..bec6222a 100644 --- a/Server/thinkphp/library/think/Config.php +++ b/Server/thinkphp/library/think/Config.php @@ -11,204 +11,388 @@ namespace think; -class Config +use Yaconf; + +class Config implements \ArrayAccess { /** - * @var array 配置参数 + * 配置参数 + * @var array */ - private static $config = []; + protected $config = []; /** - * @var string 参数作用域 + * 配置前缀 + * @var string */ - private static $range = '_sys_'; + protected $prefix = 'app'; /** - * 设定配置参数的作用域 + * 配置文件目录 + * @var string + */ + protected $path; + + /** + * 配置文件后缀 + * @var string + */ + protected $ext; + + /** + * 是否支持Yaconf + * @var bool + */ + protected $yaconf; + + /** + * 构造方法 * @access public - * @param string $range 作用域 + */ + public function __construct($path = '', $ext = '.php') + { + $this->path = $path; + $this->ext = $ext; + $this->yaconf = class_exists('Yaconf'); + } + + public static function __make(App $app) + { + $path = $app->getConfigPath(); + $ext = $app->getConfigExt(); + return new static($path, $ext); + } + + /** + * 设置开启Yaconf + * @access public + * @param bool|string $yaconf 是否使用Yaconf * @return void */ - public static function range($range) + public function setYaconf($yaconf) { - self::$range = $range; + if ($this->yaconf) { + $this->yaconf = $yaconf; + } + } - if (!isset(self::$config[$range])) self::$config[$range] = []; + /** + * 设置配置参数默认前缀 + * @access public + * @param string $prefix 前缀 + * @return void + */ + public function setDefaultPrefix($prefix) + { + $this->prefix = $prefix; } /** * 解析配置文件或内容 * @access public - * @param string $config 配置文件路径或内容 - * @param string $type 配置解析类型 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) * @return mixed */ - public static function parse($config, $type = '', $name = '', $range = '') + public function parse($config, $type = '', $name = '') { - $range = $range ?: self::$range; + if (empty($type)) { + $type = pathinfo($config, PATHINFO_EXTENSION); + } - if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION); + $object = Loader::factory($type, '\\think\\config\\driver\\', $config); - $class = false !== strpos($type, '\\') ? - $type : - '\\think\\config\\driver\\' . ucwords($type); - - return self::set((new $class())->parse($config), $name, $range); + return $this->set($object->parse(), $name); } /** - * 加载配置文件(PHP格式) + * 加载配置文件(多种格式) * @access public - * @param string $file 配置文件名 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 + * @param string $file 配置文件名 + * @param string $name 一级配置名 * @return mixed */ - public static function load($file, $name = '', $range = '') + public function load($file, $name = '') { - $range = $range ?: self::$range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; - if (is_file($file)) { - $name = strtolower($name); - $type = pathinfo($file, PATHINFO_EXTENSION); - - if ('php' == $type) { - return self::set(include $file, $name, $range); - } - - if ('yaml' == $type && function_exists('yaml_parse_file')) { - return self::set(yaml_parse_file($file), $name, $range); - } - - return self::parse($file, $type, $name, $range); + $filename = $file; + } elseif (is_file($this->path . $file . $this->ext)) { + $filename = $this->path . $file . $this->ext; } - return self::$config[$range]; + if (isset($filename)) { + return $this->loadFile($filename, $name); + } elseif ($this->yaconf && Yaconf::has($file)) { + return $this->set(Yaconf::get($file), $name); + } + + return $this->config; + } + + /** + * 获取实际的yaconf配置参数 + * @access protected + * @param string $name 配置参数名 + * @return string + */ + protected function getYaconfName($name) + { + if ($this->yaconf && is_string($this->yaconf)) { + return $this->yaconf . '.' . $name; + } + + return $name; + } + + /** + * 获取yaconf配置 + * @access public + * @param string $name 配置参数名 + * @param mixed $default 默认值 + * @return mixed + */ + public function yaconf($name, $default = null) + { + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + return Yaconf::get($yaconfName); + } + } + + return $default; + } + + protected function loadFile($file, $name) + { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + + if ('php' == $type) { + return $this->set(include $file, $name); + } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { + return $this->set(yaml_parse_file($file), $name); + } + + return $this->parse($file, $type, $name); } /** * 检测配置是否存在 * @access public - * @param string $name 配置参数名(支持二级配置 . 号分割) - * @param string $range 作用域 + * @param string $name 配置参数名(支持多级配置 .号分割) * @return bool */ - public static function has($name, $range = '') + public function has($name) { - $range = $range ?: self::$range; - - if (!strpos($name, '.')) { - return isset(self::$config[$range][strtolower($name)]); + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; } - // 二维数组设置和获取支持 - $name = explode('.', $name, 2); - return isset(self::$config[$range][strtolower($name[0])][$name[1]]); + return !is_null($this->get($name)); + } + + /** + * 获取一级配置 + * @access public + * @param string $name 一级配置名 + * @return array + */ + public function pull($name) + { + $name = strtolower($name); + + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + $config = Yaconf::get($yaconfName); + return isset($this->config[$name]) ? array_merge($this->config[$name], $config) : $config; + } + } + + return isset($this->config[$name]) ? $this->config[$name] : []; } /** * 获取配置参数 为空则获取所有配置 * @access public - * @param string $name 配置参数名(支持二级配置 . 号分割) - * @param string $range 作用域 + * @param string $name 配置参数名(支持多级配置 .号分割) + * @param mixed $default 默认值 * @return mixed */ - public static function get($name = null, $range = '') + public function get($name = null, $default = null) { - $range = $range ?: self::$range; + if ($name && false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } // 无参数时获取所有 - if (empty($name) && isset(self::$config[$range])) { - return self::$config[$range]; + if (empty($name)) { + return $this->config; } - // 非二级配置时直接返回 - if (!strpos($name, '.')) { - $name = strtolower($name); - return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; + if ('.' == substr($name, -1)) { + return $this->pull(substr($name, 0, -1)); } - // 二维数组设置和获取支持 - $name = explode('.', $name, 2); + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + return Yaconf::get($yaconfName); + } + } + + $name = explode('.', $name); $name[0] = strtolower($name[0]); + $config = $this->config; - if (!isset(self::$config[$range][$name[0]])) { - // 动态载入额外配置 - $module = Request::instance()->module(); - $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT; - - is_file($file) && self::load($file, $name[0]); + // 按.拆分成多维数组进行判断 + foreach ($name as $val) { + if (isset($config[$val])) { + $config = $config[$val]; + } else { + return $default; + } } - return isset(self::$config[$range][$name[0]][$name[1]]) ? - self::$config[$range][$name[0]][$name[1]] : - null; + return $config; } /** - * 设置配置参数 name 为数组则为批量设置 + * 设置配置参数 name为数组则为批量设置 * @access public - * @param string|array $name 配置参数名(支持二级配置 . 号分割) - * @param mixed $value 配置值 - * @param string $range 作用域 + * @param string|array $name 配置参数名(支持三级配置 .号分割) + * @param mixed $value 配置值 * @return mixed */ - public static function set($name, $value = null, $range = '') + public function set($name, $value = null) { - $range = $range ?: self::$range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; - - // 字符串则表示单个配置设置 if (is_string($name)) { - if (!strpos($name, '.')) { - self::$config[$range][strtolower($name)] = $value; + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + $this->config[strtolower($name[0])][$name[1]] = $value; } else { - // 二维数组 - $name = explode('.', $name, 2); - self::$config[$range][strtolower($name[0])][$name[1]] = $value; + $this->config[strtolower($name[0])][$name[1]][$name[2]] = $value; } return $value; - } - - // 数组则表示批量设置 - if (is_array($name)) { + } elseif (is_array($name)) { + // 批量设置 if (!empty($value)) { - self::$config[$range][$value] = isset(self::$config[$range][$value]) ? - array_merge(self::$config[$range][$value], $name) : - $name; + if (isset($this->config[$value])) { + $result = array_merge($this->config[$value], $name); + } else { + $result = $name; + } - return self::$config[$range][$value]; + $this->config[$value] = $result; + } else { + $result = $this->config = array_merge($this->config, $name); } - - return self::$config[$range] = array_merge( - self::$config[$range], array_change_key_case($name) - ); + } else { + // 为空直接返回 已有配置 + $result = $this->config; } - // 为空直接返回已有配置 - return self::$config[$range]; + return $result; + } + + /** + * 移除配置 + * @access public + * @param string $name 配置参数名(支持三级配置 .号分割) + * @return void + */ + public function remove($name) + { + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + unset($this->config[strtolower($name[0])][$name[1]]); + } else { + unset($this->config[strtolower($name[0])][$name[1]][$name[2]]); + } } /** * 重置配置参数 * @access public - * @param string $range 作用域 + * @param string $prefix 配置前缀名 * @return void */ - public static function reset($range = '') + public function reset($prefix = '') { - $range = $range ?: self::$range; - - if (true === $range) { - self::$config = []; + if ('' === $prefix) { + $this->config = []; } else { - self::$config[$range] = []; + $this->config[$prefix] = []; } } + + /** + * 设置配置 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set($name, $value) + { + return $this->set($name, $value); + } + + /** + * 获取配置参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * 检测是否存在参数 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function __isset($name) + { + return $this->has($name); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->set($name, $value); + } + + public function offsetExists($name) + { + return $this->has($name); + } + + public function offsetUnset($name) + { + $this->remove($name); + } + + public function offsetGet($name) + { + return $this->get($name); + } } diff --git a/Server/thinkphp/library/think/Console.php b/Server/thinkphp/library/think/Console.php index 32b25725..22f3e2c5 100644 --- a/Server/thinkphp/library/think/Console.php +++ b/Server/thinkphp/library/think/Console.php @@ -20,60 +20,37 @@ use think\console\output\driver\Buffer; class Console { - /** - * @var string 命令名称 - */ - private $name; - /** - * @var string 命令版本 - */ + private $name; private $version; - /** - * @var Command[] 命令 - */ + /** @var Command[] */ private $commands = []; - /** - * @var bool 是否需要帮助信息 - */ private $wantHelps = false; - /** - * @var bool 是否捕获异常 - */ private $catchExceptions = true; - - /** - * @var bool 是否自动退出执行 - */ - private $autoExit = true; - - /** - * @var InputDefinition 输入定义 - */ + private $autoExit = true; private $definition; - - /** - * @var string 默认执行的命令 - */ private $defaultCommand; - /** - * @var array 默认提供的命令 - */ private static $defaultCommands = [ - "think\\console\\command\\Help", - "think\\console\\command\\Lists", - "think\\console\\command\\Build", - "think\\console\\command\\Clear", - "think\\console\\command\\make\\Controller", - "think\\console\\command\\make\\Model", - "think\\console\\command\\optimize\\Autoload", - "think\\console\\command\\optimize\\Config", - "think\\console\\command\\optimize\\Route", - "think\\console\\command\\optimize\\Schema", + 'help' => "think\\console\\command\\Help", + 'list' => "think\\console\\command\\Lists", + 'build' => "think\\console\\command\\Build", + 'clear' => "think\\console\\command\\Clear", + 'make:command' => "think\\console\\command\\make\\Command", + 'make:controller' => "think\\console\\command\\make\\Controller", + 'make:model' => "think\\console\\command\\make\\Model", + 'make:middleware' => "think\\console\\command\\make\\Middleware", + 'make:validate' => "think\\console\\command\\make\\Validate", + 'optimize:autoload' => "think\\console\\command\\optimize\\Autoload", + 'optimize:config' => "think\\console\\command\\optimize\\Config", + 'optimize:schema' => "think\\console\\command\\optimize\\Schema", + 'optimize:route' => "think\\console\\command\\optimize\\Route", + 'run' => "think\\console\\command\\RunServer", + 'version' => "think\\console\\command\\Version", + 'route:list' => "think\\console\\command\\RouteList", ]; /** @@ -94,10 +71,6 @@ class Console $this->defaultCommand = 'list'; $this->definition = $this->getDefaultInputDefinition(); - - foreach ($this->getDefaultCommands() as $command) { - $this->add($command); - } } /** @@ -106,6 +79,10 @@ class Console */ public function setUser($user) { + if (DIRECTORY_SEPARATOR == '\\') { + return; + } + $user = posix_getpwnam($user); if ($user) { posix_setuid($user['uid']); @@ -124,34 +101,69 @@ class Console static $console; if (!$console) { - $config = Config::get('console'); - // 实例化 console + $config = Container::get('config')->pull('console'); $console = new self($config['name'], $config['version'], $config['user']); - // 读取指令集 - if (is_file(CONF_PATH . 'command' . EXT)) { - $commands = include CONF_PATH . 'command' . EXT; + $commands = $console->getDefinedCommands($config); - if (is_array($commands)) { - foreach ($commands as $command) { - class_exists($command) && - is_subclass_of($command, "\\think\\console\\Command") && - $console->add(new $command()); // 注册指令 - } - } - } + // 添加指令集 + $console->addCommands($commands); } - return $run ? $console->run() : $console; + if ($run) { + // 运行 + return $console->run(); + } else { + return $console; + } + } + + /** + * @access public + * @param array $config + * @return array + */ + public function getDefinedCommands(array $config = []) + { + $commands = self::$defaultCommands; + + if (!empty($config['auto_path']) && is_dir($config['auto_path'])) { + // 自动加载指令类 + $files = scandir($config['auto_path']); + + if (count($files) > 2) { + $beforeClass = get_declared_classes(); + + foreach ($files as $file) { + if (pathinfo($file, PATHINFO_EXTENSION) == 'php') { + include $config['auto_path'] . $file; + } + } + + $afterClass = get_declared_classes(); + $commands = array_merge($commands, array_diff($afterClass, $beforeClass)); + } + } + + $file = Container::get('env')->get('app_path') . 'command.php'; + + if (is_file($file)) { + $appCommands = include $file; + + if (is_array($appCommands)) { + $commands = array_merge($commands, $appCommands); + } + } + + return $commands; } /** - * 调用命令 * @access public * @param string $command * @param array $parameters * @param string $driver - * @return Output + * @return Output|Buffer */ public static function call($command, array $parameters = [], $driver = 'buffer') { @@ -173,6 +185,7 @@ class Console * @access public * @return int * @throws \Exception + * @api */ public function run() { @@ -184,21 +197,27 @@ class Console try { $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { - if (!$this->catchExceptions) throw $e; + if (!$this->catchExceptions) { + throw $e; + } $output->renderException($e); $exitCode = $e->getCode(); - if (is_numeric($exitCode)) { - $exitCode = ((int) $exitCode) ?: 1; + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } } else { $exitCode = 1; } } if ($this->autoExit) { - if ($exitCode > 255) $exitCode = 255; + if ($exitCode > 255) { + $exitCode = 255; + } exit($exitCode); } @@ -209,13 +228,12 @@ class Console /** * 执行指令 * @access public - * @param Input $input 输入 - * @param Output $output 输出 + * @param Input $input + * @param Output $output * @return int */ public function doRun(Input $input, Output $output) { - // 获取版本信息 if (true === $input->hasParameterOption(['--version', '-V'])) { $output->writeln($this->getLongVersion()); @@ -224,7 +242,6 @@ class Console $name = $this->getCommandName($input); - // 获取帮助信息 if (true === $input->hasParameterOption(['--help', '-h'])) { if (!$name) { $name = 'help'; @@ -239,26 +256,27 @@ class Console $input = new Input([$this->defaultCommand]); } - return $this->doRunCommand($this->find($name), $input, $output); + $command = $this->find($name); + + $exitCode = $this->doRunCommand($command, $input, $output); + + return $exitCode; } /** * 设置输入参数定义 * @access public - * @param InputDefinition $definition 输入定义 - * @return $this; + * @param InputDefinition $definition */ public function setDefinition(InputDefinition $definition) { $this->definition = $definition; - - return $this; } /** * 获取输入参数定义 * @access public - * @return InputDefinition + * @return InputDefinition The InputDefinition instance */ public function getDefinition() { @@ -266,9 +284,9 @@ class Console } /** - * 获取帮助信息 + * Gets the help message. * @access public - * @return string + * @return string A help message. */ public function getHelp() { @@ -276,29 +294,25 @@ class Console } /** - * 设置是否捕获异常 + * 是否捕获异常 * @access public - * @param bool $boolean 是否捕获 - * @return $this + * @param bool $boolean + * @api */ public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; - - return $this; } /** - * 设置是否自动退出 + * 是否自动退出 * @access public - * @param bool $boolean 是否自动退出 - * @return $this + * @param bool $boolean + * @api */ public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; - - return $this; } /** @@ -314,20 +328,18 @@ class Console /** * 设置名称 * @access public - * @param string $name 名称 - * @return $this + * @param string $name */ public function setName($name) { $this->name = $name; - - return $this; } /** * 获取版本 * @access public * @return string + * @api */ public function getVersion() { @@ -337,14 +349,11 @@ class Console /** * 设置版本 * @access public - * @param string $version 版本信息 - * @return $this + * @param string $version */ public function setVersion($version) { $this->version = $version; - - return $this; } /** @@ -355,20 +364,16 @@ class Console public function getLongVersion() { if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { - return sprintf( - '%s version %s', - $this->getName(), - $this->getVersion() - ); + return sprintf('%s version %s', $this->getName(), $this->getVersion()); } return 'Console Tool'; } /** - * 注册一个指令 + * 注册一个指令 (便于动态创建指令) * @access public - * @param string $name 指令名称 + * @param string $name 指令名 * @return Command */ public function register($name) @@ -377,37 +382,47 @@ class Console } /** - * 批量添加指令 + * 添加指令集 * @access public - * @param Command[] $commands 指令实例 - * @return $this + * @param array $commands */ public function addCommands(array $commands) { - foreach ($commands as $command) $this->add($command); - - return $this; + foreach ($commands as $key => $command) { + if (is_subclass_of($command, "\\think\\console\\Command")) { + // 注册指令 + $this->add($command, is_numeric($key) ? '' : $key); + } + } } /** - * 添加一个指令 + * 注册一个指令(对象) * @access public - * @param Command $command 命令实例 - * @return Command|bool + * @param mixed $command 指令对象或者指令类名 + * @param string $name 指令名 留空则自动获取 + * @return mixed */ - public function add(Command $command) + public function add($command, $name) { - if (!$command->isEnabled()) { - $command->setConsole(null); - return false; + if ($name) { + $this->commands[$name] = $command; + return; + } + + if (is_string($command)) { + $command = new $command(); } $command->setConsole($this); + if (!$command->isEnabled()) { + $command->setConsole(null); + return; + } + if (null === $command->getDefinition()) { - throw new \LogicException( - sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)) - ); + throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); } $this->commands[$command->getName()] = $command; @@ -429,13 +444,17 @@ class Console public function get($name) { if (!isset($this->commands[$name])) { - throw new \InvalidArgumentException( - sprintf('The command "%s" does not exist.', $name) - ); + throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); } $command = $this->commands[$name]; + if (is_string($command)) { + $command = new $command(); + } + + $command->setConsole($this); + if ($this->wantHelps) { $this->wantHelps = false; @@ -468,42 +487,39 @@ class Console public function getNamespaces() { $namespaces = []; + foreach ($this->commands as $name => $command) { + if (is_string($command)) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($name)); + } else { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); - foreach ($this->commands as $command) { - $namespaces = array_merge( - $namespaces, $this->extractAllNamespaces($command->getName()) - ); - - foreach ($command->getAliases() as $alias) { - $namespaces = array_merge( - $namespaces, $this->extractAllNamespaces($alias) - ); + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } } + } return array_values(array_unique(array_filter($namespaces))); } /** - * 查找注册命名空间中的名称或缩写 + * 查找注册命名空间中的名称或缩写。 * @access public - * @param string $namespace + * @param string $namespace * @return string * @throws \InvalidArgumentException */ public function findNamespace($namespace) { - $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]) . '[^:]*'; }, $namespace); - - $allNamespaces = $this->getNamespaces(); - $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); if (empty($namespaces)) { - $message = sprintf( - 'There are no commands defined in the "%s" namespace.', $namespace - ); + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { if (1 == count($alternatives)) { @@ -519,14 +535,8 @@ class Console } $exact = in_array($namespace, $namespaces, true); - if (count($namespaces) > 1 && !$exact) { - throw new \InvalidArgumentException( - sprintf( - 'The namespace "%s" is ambiguous (%s).', - $namespace, - $this->getAbbreviationSuggestions(array_values($namespaces))) - ); + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); } return $exact ? $namespace : reset($namespaces); @@ -541,15 +551,16 @@ class Console */ public function find($name) { + $allCommands = array_keys($this->commands); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]) . '[^:]*'; }, $name); - $allCommands = array_keys($this->commands); - $commands = preg_grep('{^' . $expr . '}', $allCommands); + $commands = preg_grep('{^' . $expr . '}', $allCommands); if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { - if (false !== ($pos = strrpos($name, ':'))) { + if (false !== $pos = strrpos($name, ':')) { $this->findNamespace(substr($name, 0, $pos)); } @@ -567,22 +578,11 @@ class Console throw new \InvalidArgumentException($message); } - if (count($commands) > 1) { - $commandList = $this->commands; - $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { - $commandName = $commandList[$nameOrAlias]->getName(); - - return $commandName === $nameOrAlias || !in_array($commandName, $commands); - }); - } - $exact = in_array($name, $commands, true); if (count($commands) > 1 && !$exact) { $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); - throw new \InvalidArgumentException( - sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions) - ); + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); } return $this->get($exact ? $name : reset($commands)); @@ -593,17 +593,19 @@ class Console * @access public * @param string $namespace 命名空间 * @return Command[] + * @api */ public function all($namespace = null) { - if (null === $namespace) return $this->commands; + if (null === $namespace) { + return $this->commands; + } $commands = []; - foreach ($this->commands as $name => $command) { - $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1); - - if ($ext === $namespace) $commands[$name] = $command; + if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { + $commands[$name] = $command; + } } return $commands; @@ -612,7 +614,7 @@ class Console /** * 获取可能的指令名 * @access public - * @param array $names 指令名 + * @param array $names * @return array */ public static function getAbbreviations($names) @@ -629,11 +631,10 @@ class Console } /** - * 配置基于用户的参数和选项的输入和输出实例 + * 配置基于用户的参数和选项的输入和输出实例。 * @access protected * @param Input $input 输入实例 * @param Output $output 输出实例 - * @return void */ protected function configureIO(Input $input, Output $output) { @@ -675,9 +676,9 @@ class Console } /** - * 获取指令的名称 + * 获取指令的基础名称 * @access protected - * @param Input $input 输入实例 + * @param Input $input * @return string */ protected function getCommandName(Input $input) @@ -704,33 +705,9 @@ class Console ]); } - /** - * 获取默认命令 - * @access protected - * @return Command[] - */ - protected function getDefaultCommands() + public static function addDefaultCommands(array $classnames) { - $defaultCommands = []; - - foreach (self::$defaultCommands as $class) { - if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) { - $defaultCommands[] = new $class(); - } - } - - return $defaultCommands; - } - - /** - * 添加默认指令 - * @access public - * @param array $classes 指令 - * @return void - */ - public static function addDefaultCommands(array $classes) - { - self::$defaultCommands = array_merge(self::$defaultCommands, $classes); + self::$defaultCommands = array_merge(self::$defaultCommands, $classnames); } /** @@ -741,18 +718,13 @@ class Console */ private function getAbbreviationSuggestions($abbrevs) { - return sprintf( - '%s, %s%s', - $abbrevs[0], - $abbrevs[1], - count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '' - ); + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } /** - * 返回指令的命名空间部分 + * 返回命名空间部分 * @access public - * @param string $name 指令名称 + * @param string $name 指令 * @param string $limit 部分的命名空间的最大数量 * @return string */ @@ -767,16 +739,16 @@ class Console /** * 查找可替代的建议 * @access private - * @param string $name 指令名称 - * @param array|\Traversable $collection 建议集合 + * @param string $name + * @param array|\Traversable $collection * @return array */ private function findAlternatives($name, $collection) { - $threshold = 1e3; - $alternatives = []; - $collectionParts = []; + $threshold = 1e3; + $alternatives = []; + $collectionParts = []; foreach ($collection as $item) { $collectionParts[$item] = explode(':', $item); } @@ -784,7 +756,6 @@ class Console foreach (explode(':', $name) as $i => $subname) { foreach ($collectionParts as $collectionName => $parts) { $exists = isset($alternatives[$collectionName]); - if (!isset($parts[$i]) && $exists) { $alternatives[$collectionName] += $threshold; continue; @@ -793,14 +764,8 @@ class Console } $lev = levenshtein($subname, $parts[$i]); - - if ($lev <= strlen($subname) / 3 || - '' !== $subname && - false !== strpos($parts[$i], $subname) - ) { - $alternatives[$collectionName] = $exists ? - $alternatives[$collectionName] + $lev : - $lev; + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; } elseif ($exists) { $alternatives[$collectionName] += $threshold; } @@ -809,18 +774,14 @@ class Console foreach ($collection as $item) { $lev = levenshtein($name, $item); - if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { - $alternatives[$item] = isset($alternatives[$item]) ? - $alternatives[$item] - $lev : - $lev; + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; } } $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); - asort($alternatives); return array_keys($alternatives); @@ -829,27 +790,25 @@ class Console /** * 设置默认的指令 * @access public - * @param string $commandName 指令名称 - * @return $this + * @param string $commandName The Command name */ public function setDefaultCommand($commandName) { $this->defaultCommand = $commandName; - - return $this; } /** * 返回所有的命名空间 * @access private - * @param string $name 指令名称 + * @param string $name * @return array */ private function extractAllNamespaces($name) { + $parts = explode(':', $name, -1); $namespaces = []; - foreach (explode(':', $name, -1) as $part) { + foreach ($parts as $part) { if (count($namespaces)) { $namespaces[] = end($namespaces) . ':' . $part; } else { @@ -860,4 +819,11 @@ class Console return $namespaces; } + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['commands'], $data['definition']); + + return $data; + } } diff --git a/Server/thinkphp/library/think/Controller.php b/Server/thinkphp/library/think/Controller.php index 77225b73..966eaaa8 100644 --- a/Server/thinkphp/library/think/Controller.php +++ b/Server/thinkphp/library/think/Controller.php @@ -14,74 +14,108 @@ namespace think; use think\exception\ValidateException; use traits\controller\Jump; -Loader::import('controller/Jump', TRAIT_PATH, EXT); - class Controller { use Jump; /** - * @var \think\View 视图类实例 + * 视图类实例 + * @var \think\View */ protected $view; /** - * @var \think\Request Request 实例 + * Request实例 + * @var \think\Request */ protected $request; /** - * @var bool 验证失败是否抛出异常 + * 验证失败是否抛出异常 + * @var bool */ protected $failException = false; /** - * @var bool 是否批量验证 + * 是否批量验证 + * @var bool */ protected $batchValidate = false; /** - * @var array 前置操作方法列表 + * 前置操作方法列表(即将废弃) + * @var array $beforeActionList */ protected $beforeActionList = []; + /** + * 控制器中间件 + * @var array + */ + protected $middleware = []; + /** * 构造方法 * @access public - * @param Request $request Request 对象 */ - public function __construct(Request $request = null) + public function __construct(App $app = null) { - $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); - $this->request = is_null($request) ? Request::instance() : $request; + $this->app = $app ?: Container::get('app'); + $this->request = $this->app['request']; + $this->view = $this->app['view']; // 控制器初始化 - $this->_initialize(); + $this->initialize(); - // 前置操作方法 - if ($this->beforeActionList) { - foreach ($this->beforeActionList as $method => $options) { - is_numeric($method) ? - $this->beforeAction($options) : - $this->beforeAction($method, $options); - } + $this->registerMiddleware(); + + // 前置操作方法 即将废弃 + foreach ((array) $this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); } } - /** - * 初始化操作 - * @access protected - */ - protected function _initialize() + // 初始化 + protected function initialize() + {} + + // 注册控制器中间件 + public function registerMiddleware() { + foreach ($this->middleware as $key => $val) { + if (!is_int($key)) { + $only = $except = null; + + if (isset($val['only'])) { + $only = array_map(function ($item) { + return strtolower($item); + }, $val['only']); + } elseif (isset($val['except'])) { + $except = array_map(function ($item) { + return strtolower($item); + }, $val['except']); + } + + if (isset($only) && !in_array($this->request->action(), $only)) { + continue; + } elseif (isset($except) && in_array($this->request->action(), $except)) { + continue; + } else { + $val = $key; + } + } + + $this->app['middleware']->controller($val); + } } /** * 前置操作 * @access protected * @param string $method 前置操作方法名 - * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]] - * @return void + * @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]] */ protected function beforeAction($method, $options = []) { @@ -90,7 +124,11 @@ class Controller $options['only'] = explode(',', $options['only']); } - if (!in_array($this->request->action(), $options['only'])) { + $only = array_map(function ($val) { + return strtolower($val); + }, $options['only']); + + if (!in_array($this->request->action(), $only)) { return; } } elseif (isset($options['except'])) { @@ -98,7 +136,11 @@ class Controller $options['except'] = explode(',', $options['except']); } - if (in_array($this->request->action(), $options['except'])) { + $except = array_map(function ($val) { + return strtolower($val); + }, $options['except']); + + if (in_array($this->request->action(), $except)) { return; } } @@ -111,13 +153,12 @@ class Controller * @access protected * @param string $template 模板文件名 * @param array $vars 模板输出变量 - * @param array $replace 模板替换 * @param array $config 模板参数 * @return mixed */ - protected function fetch($template = '', $vars = [], $replace = [], $config = []) + protected function fetch($template = '', $vars = [], $config = []) { - return $this->view->fetch($template, $vars, $replace, $config); + return Response::create($template, 'view')->assign($vars)->config($config); } /** @@ -125,13 +166,12 @@ class Controller * @access protected * @param string $content 模板内容 * @param array $vars 模板输出变量 - * @param array $replace 替换内容 * @param array $config 模板参数 * @return mixed */ - protected function display($content = '', $vars = [], $replace = [], $config = []) + protected function display($content = '', $vars = [], $config = []) { - return $this->view->display($content, $vars, $replace, $config); + return Response::create($content, 'view')->assign($vars)->config($config)->isContent(true); } /** @@ -148,10 +188,23 @@ class Controller return $this; } + /** + * 视图过滤 + * @access protected + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + protected function filter($filter) + { + $this->view->filter($filter); + + return $this; + } + /** * 初始化模板引擎 * @access protected - * @param array|string $engine 引擎参数 + * @param array|string $engine 引擎参数 * @return $this */ protected function engine($engine) @@ -164,7 +217,7 @@ class Controller /** * 设置验证失败后是否抛出异常 * @access protected - * @param bool $fail 是否抛出异常 + * @param bool $fail 是否抛出异常 * @return $this */ protected function validateFailException($fail = true) @@ -188,30 +241,28 @@ class Controller protected function validate($data, $validate, $message = [], $batch = false, $callback = null) { if (is_array($validate)) { - $v = Loader::validate(); + $v = $this->app->validate(); $v->rule($validate); } else { - // 支持场景 if (strpos($validate, '.')) { + // 支持场景 list($validate, $scene) = explode('.', $validate); } - - $v = Loader::validate($validate); - - !empty($scene) && $v->scene($scene); + $v = $this->app->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } } - // 批量验证 + // 是否批量验证 if ($batch || $this->batchValidate) { $v->batch(true); } - // 设置错误信息 if (is_array($message)) { $v->message($message); } - // 使用回调验证 if ($callback && is_callable($callback)) { call_user_func_array($callback, [$v, &$data]); } @@ -220,10 +271,17 @@ class Controller if ($this->failException) { throw new ValidateException($v->getError()); } - return $v->getError(); } return true; } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request']); + + return $data; + } } diff --git a/Server/thinkphp/library/think/Cookie.php b/Server/thinkphp/library/think/Cookie.php index 61b47cce..6a9fb1ee 100644 --- a/Server/thinkphp/library/think/Cookie.php +++ b/Server/thinkphp/library/think/Cookie.php @@ -14,71 +14,81 @@ namespace think; class Cookie { /** - * @var array cookie 设置参数 + * 配置参数 + * @var array */ - protected static $config = [ - 'prefix' => '', // cookie 名称前缀 - 'expire' => 0, // cookie 保存时间 - 'path' => '/', // cookie 保存路径 - 'domain' => '', // cookie 有效域名 - 'secure' => false, // cookie 启用安全传输 - 'httponly' => false, // httponly 设置 - 'setcookie' => true, // 是否使用 setcookie + protected $config = [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, ]; /** - * @var bool 是否完成初始化了 + * 构造方法 + * @access public */ - protected static $init; + public function __construct(array $config = []) + { + $this->init($config); + } /** * Cookie初始化 * @access public - * @param array $config 配置参数 + * @param array $config * @return void */ - public static function init(array $config = []) + public function init(array $config = []) { - if (empty($config)) { - $config = Config::get('cookie'); - } + $this->config = array_merge($this->config, array_change_key_case($config)); - self::$config = array_merge(self::$config, array_change_key_case($config)); - - if (!empty(self::$config['httponly'])) { + if (!empty($this->config['httponly']) && PHP_SESSION_ACTIVE != session_status()) { ini_set('session.cookie_httponly', 1); } + } - self::$init = true; + public static function __make(Config $config) + { + return new static($config->pull('cookie')); } /** - * 设置或者获取 cookie 作用域(前缀) + * 设置或者获取cookie作用域(前缀) * @access public - * @param string $prefix 前缀 - * @return string| + * @param string $prefix + * @return string|void */ - public static function prefix($prefix = '') + public function prefix($prefix = '') { if (empty($prefix)) { - return self::$config['prefix']; + return $this->config['prefix']; } - return self::$config['prefix'] = $prefix; + $this->config['prefix'] = $prefix; } /** * Cookie 设置、获取、删除 + * * @access public - * @param string $name cookie 名称 - * @param mixed $value cookie 值 + * @param string $name cookie名称 + * @param mixed $value cookie值 * @param mixed $option 可选参数 可能会是 null|integer|string * @return void */ - public static function set($name, $value = '', $option = null) + public function set($name, $value = '', $option = null) { - !isset(self::$init) && self::init(); - // 参数设置(会覆盖黙认设置) if (!is_null($option)) { if (is_numeric($option)) { @@ -87,42 +97,51 @@ class Cookie parse_str($option, $option); } - $config = array_merge(self::$config, array_change_key_case($option)); + $config = array_merge($this->config, array_change_key_case($option)); } else { - $config = self::$config; + $config = $this->config; } $name = $config['prefix'] . $name; - // 设置 cookie + // 设置cookie if (is_array($value)) { - array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'encode'); $value = 'think:' . json_encode($value); } - $expire = !empty($config['expire']) ? - $_SERVER['REQUEST_TIME'] + intval($config['expire']) : - 0; + $expire = !empty($config['expire']) ? $_SERVER['REQUEST_TIME'] + intval($config['expire']) : 0; if ($config['setcookie']) { - setcookie( - $name, $value, $expire, $config['path'], $config['domain'], - $config['secure'], $config['httponly'] - ); + $this->setCookie($name, $value, $expire, $config); } $_COOKIE[$name] = $value; } /** - * 永久保存 Cookie 数据 + * Cookie 设置保存 + * * @access public - * @param string $name cookie 名称 - * @param mixed $value cookie 值 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param array $option 可选参数 + * @return void + */ + protected function setCookie($name, $value, $expire, $option = []) + { + setcookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'], $option['httponly']); + } + + /** + * 永久保存Cookie数据 + * @access public + * @param string $name cookie名称 + * @param mixed $value cookie值 * @param mixed $option 可选参数 可能会是 null|integer|string * @return void */ - public static function forever($name, $value = '', $option = null) + public function forever($name, $value = '', $option = null) { if (is_null($option) || is_numeric($option)) { $option = []; @@ -130,49 +149,43 @@ class Cookie $option['expire'] = 315360000; - self::set($name, $value, $option); + $this->set($name, $value, $option); } /** - * 判断是否有 Cookie 数据 + * 判断Cookie数据 * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 * @return bool */ - public static function has($name, $prefix = null) + public function has($name, $prefix = null) { - !isset(self::$init) && self::init(); + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $name = $prefix . $name; - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; - - return isset($_COOKIE[$prefix . $name]); + return isset($_COOKIE[$name]); } /** - * 获取 Cookie 的值 + * Cookie获取 * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 + * @param string $name cookie名称 留空获取全部 + * @param string|null $prefix cookie前缀 * @return mixed */ - public static function get($name = '', $prefix = null) + public function get($name = '', $prefix = null) { - !isset(self::$init) && self::init(); - - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; $key = $prefix . $name; if ('' == $name) { - // 获取全部 if ($prefix) { $value = []; - foreach ($_COOKIE as $k => $val) { if (0 === strpos($k, $prefix)) { $value[$k] = $val; } - } } else { $value = $_COOKIE; @@ -181,8 +194,9 @@ class Cookie $value = $_COOKIE[$key]; if (0 === strpos($value, 'think:')) { - $value = json_decode(substr($value, 6), true); - array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); + $value = substr($value, 6); + $value = json_decode($value, true); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'decode'); } } else { $value = null; @@ -192,77 +206,63 @@ class Cookie } /** - * 删除 Cookie + * Cookie删除 * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 * @return void */ - public static function delete($name, $prefix = null) + public function delete($name, $prefix = null) { - !isset(self::$init) && self::init(); - - $config = self::$config; + $config = $this->config; $prefix = !is_null($prefix) ? $prefix : $config['prefix']; $name = $prefix . $name; if ($config['setcookie']) { - setcookie( - $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], - $config['domain'], $config['secure'], $config['httponly'] - ); + $this->setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, $config); } - // 删除指定 cookie + // 删除指定cookie unset($_COOKIE[$name]); } /** - * 清除指定前缀的所有 cookie + * Cookie清空 * @access public - * @param string|null $prefix cookie 前缀 + * @param string|null $prefix cookie前缀 * @return void */ - public static function clear($prefix = null) + public function clear($prefix = null) { + // 清除指定前缀的所有cookie if (empty($_COOKIE)) { return; } - !isset(self::$init) && self::init(); - - // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀 - $config = self::$config; + // 要删除的cookie前缀,不指定则删除config设置的指定前缀 + $config = $this->config; $prefix = !is_null($prefix) ? $prefix : $config['prefix']; if ($prefix) { + // 如果前缀为空字符串将不作处理直接返回 foreach ($_COOKIE as $key => $val) { if (0 === strpos($key, $prefix)) { if ($config['setcookie']) { - setcookie( - $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], - $config['domain'], $config['secure'], $config['httponly'] - ); + $this->setcookie($key, '', $_SERVER['REQUEST_TIME'] - 3600, $config); } - unset($_COOKIE[$key]); } } } + + return; } - /** - * json 转换时的格式保护 - * @access protected - * @param mixed $val 要转换的值 - * @param string $key 键名 - * @param string $type 转换类别 - * @return void - */ - protected static function jsonFormatProtect(&$val, $key, $type = 'encode') + private function jsonFormatProtect(&$val, $key, $type = 'encode') { if (!empty($val) && true !== $val) { $val = 'decode' == $type ? urldecode($val) : urlencode($val); } } + } diff --git a/Server/thinkphp/library/think/Db.php b/Server/thinkphp/library/think/Db.php index 80f08d24..9280eac0 100644 --- a/Server/thinkphp/library/think/Db.php +++ b/Server/thinkphp/library/think/Db.php @@ -12,22 +12,31 @@ namespace think; use think\db\Connection; -use think\db\Query; /** * Class Db * @package think - * @method Query table(string $table) static 指定数据表(含前缀) - * @method Query name(string $name) static 指定数据表(不含前缀) - * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 - * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 - * @method Query union(mixed $union, boolean $all = false) static UNION查询 - * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT - * @method Query order(mixed $field, string $order = null) static 查询ORDER - * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method \think\db\Query master() static 从主服务器读取数据 + * @method \think\db\Query readMaster(bool $all = false) static 后续从主服务器读取数据 + * @method \think\db\Query table(string $table) static 指定数据表(含前缀) + * @method \think\db\Query name(string $name) static 指定数据表(不含前缀) + * @method \think\db\Expression raw(string $value) static 使用表达式设置数据 + * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method \think\db\Query whereRaw(string $where, array $bind = []) static 表达式查询 + * @method \think\db\Query whereExp(string $field, string $condition, array $bind = []) static 字段表达式查询 + * @method \think\db\Query when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询 + * @method \think\db\Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method \think\db\Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method \think\db\Query field(mixed $field, boolean $except = false) static 指定查询字段 + * @method \think\db\Query fieldRaw(string $field, array $bind = []) static 指定查询字段 + * @method \think\db\Query union(mixed $union, boolean $all = false) static UNION查询 + * @method \think\db\Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method \think\db\Query order(mixed $field, string $order = null) static 查询ORDER + * @method \think\db\Query orderRaw(string $field, array $bind = []) static 查询ORDER + * @method \think\db\Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method \think\db\Query withAttr(string $name,callable $callback = null) static 使用获取器获取数据 * @method mixed value(string $field) static 获取某个字段的值 * @method array column(string $field, string $key = '') static 获取某个列的值 - * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 * @method mixed find(mixed $data = null) static 查询单个记录 * @method mixed select(mixed $data = null) static 查询多个记录 * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 @@ -36,110 +45,125 @@ use think\db\Query; * @method integer update(array $data) static 更新记录 * @method integer delete(mixed $data = null) static 删除记录 * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 + * @method \Generator cursor(mixed $data = null) static 使用游标查找记录 * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 - * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 + * @method \think\Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 * @method mixed transaction(callable $callback) static 执行数据库事务 * @method void startTrans() static 启动事务 * @method void commit() static 用于非自动提交状态下面的查询提交 * @method void rollback() static 事务回滚 * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 - * @method string quote(string $str) static SQL指令安全过滤 - * @method string getLastInsID($sequence = null) static 获取最近插入的ID + * @method string getLastInsID(string $sequence = null) static 获取最近插入的ID */ class Db { /** - * @var Connection[] 数据库连接实例 + * 当前数据库连接对象 + * @var Connection */ - private static $instance = []; + protected static $connection; /** - * @var int 查询次数 + * 数据库配置 + * @var array + */ + protected static $config = []; + + /** + * 查询次数 + * @var integer */ public static $queryTimes = 0; /** - * @var int 执行次数 + * 执行次数 + * @var integer */ public static $executeTimes = 0; /** - * 数据库初始化,并取得数据库类实例 + * 配置 * @access public - * @param mixed $config 连接配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return Connection - * @throws Exception + * @param mixed $config + * @return void */ - public static function connect($config = [], $name = false) + public static function init($config = []) { - if (false === $name) { - $name = md5(serialize($config)); + self::$config = $config; + + if (empty($config['query'])) { + self::$config['query'] = '\\think\\db\\Query'; } - - if (true === $name || !isset(self::$instance[$name])) { - // 解析连接参数 支持数组和字符串 - $options = self::parseConfig($config); - - if (empty($options['type'])) { - throw new \InvalidArgumentException('Undefined db type'); - } - - $class = false !== strpos($options['type'], '\\') ? - $options['type'] : - '\\think\\db\\connector\\' . ucwords($options['type']); - - // 记录初始化信息 - if (App::$debug) { - Log::record('[ DB ] INIT ' . $options['type'], 'info'); - } - - if (true === $name) { - $name = md5(serialize($config)); - } - - self::$instance[$name] = new $class($options); - } - - return self::$instance[$name]; } /** - * 清除连接实例 + * 获取数据库配置 * @access public - * @return void + * @param string $config 配置名称 + * @return mixed */ - public static function clear() + public static function getConfig($name = '') { - self::$instance = []; + if ('' === $name) { + return self::$config; + } + + return isset(self::$config[$name]) ? self::$config[$name] : null; + } + + /** + * 切换数据库连接 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @param string $query 查询对象类名 + * @return mixed 返回查询对象实例 + * @throws Exception + */ + public static function connect($config = [], $name = false, $query = '') + { + // 解析配置参数 + $options = self::parseConfig($config ?: self::$config); + + $query = $query ?: $options['query']; + + // 创建数据库连接对象实例 + self::$connection = Connection::instance($options, $name); + + return new $query(self::$connection); } /** * 数据库连接参数解析 * @access private - * @param mixed $config 连接参数 + * @param mixed $config * @return array */ private static function parseConfig($config) { - if (empty($config)) { - $config = Config::get('database'); - } elseif (is_string($config) && false === strpos($config, '/')) { - $config = Config::get($config); // 支持读取配置参数 + if (is_string($config) && false === strpos($config, '/')) { + // 支持读取配置参数 + $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config; } - return is_string($config) ? self::parseDsn($config) : $config; + $result = is_string($config) ? self::parseDsnConfig($config) : $config; + + if (empty($result['query'])) { + $result['query'] = self::$config['query']; + } + + return $result; } /** - * DSN 解析 + * DSN解析 * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 * @access private - * @param string $dsnStr 数据库 DSN 字符串解析 + * @param string $dsnStr * @return array */ - private static function parseDsn($dsnStr) + private static function parseDsnConfig($dsnStr) { $info = parse_url($dsnStr); @@ -166,15 +190,8 @@ class Db return $dsn; } - /** - * 调用驱动类的方法 - * @access public - * @param string $method 方法名 - * @param array $params 参数 - * @return mixed - */ - public static function __callStatic($method, $params) + public static function __callStatic($method, $args) { - return call_user_func_array([self::connect(), $method], $params); + return call_user_func_array([static::connect(), $method], $args); } } diff --git a/Server/thinkphp/library/think/Debug.php b/Server/thinkphp/library/think/Debug.php index df487485..776e1787 100644 --- a/Server/thinkphp/library/think/Debug.php +++ b/Server/thinkphp/library/think/Debug.php @@ -11,64 +11,95 @@ namespace think; -use think\exception\ClassNotFoundException; +use think\model\Collection as ModelCollection; use think\response\Redirect; class Debug { /** - * @var array 区间时间信息 + * 配置参数 + * @var array */ - protected static $info = []; + protected $config = []; /** - * @var array 区间内存信息 + * 区间时间信息 + * @var array */ - protected static $mem = []; + protected $info = []; + + /** + * 区间内存信息 + * @var array + */ + protected $mem = []; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = $config; + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('trace')); + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } /** * 记录时间(微秒)和内存使用情况 * @access public - * @param string $name 标记位置 - * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存) + * @param string $name 标记位置 + * @param mixed $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存 * @return void */ - public static function remark($name, $value = '') + public function remark($name, $value = '') { - self::$info[$name] = is_float($value) ? $value : microtime(true); + // 记录时间和内存使用 + $this->info[$name] = is_float($value) ? $value : microtime(true); if ('time' != $value) { - self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); - self::$mem['peak'][$name] = memory_get_peak_usage(); + $this->mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + $this->mem['peak'][$name] = memory_get_peak_usage(); } } /** - * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 + * 统计某个区间的时间(微秒)使用情况 * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 - * @return string + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return integer */ - public static function getRangeTime($start, $end, $dec = 6) + public function getRangeTime($start, $end, $dec = 6) { - if (!isset(self::$info[$end])) { - self::$info[$end] = microtime(true); + if (!isset($this->info[$end])) { + $this->info[$end] = microtime(true); } - return number_format((self::$info[$end] - self::$info[$start]), $dec); + return number_format(($this->info[$end] - $this->info[$start]), $dec); } /** - * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 + * 统计从开始到统计时的时间(微秒)使用情况 * @access public - * @param integer $dec 小数位 - * @return string + * @param integer|string $dec 小数位 + * @return integer */ - public static function getUseTime($dec = 6) + public function getUseTime($dec = 6) { - return number_format((microtime(true) - THINK_START_TIME), $dec); + return number_format((microtime(true) - $this->app->getBeginTime()), $dec); } /** @@ -76,26 +107,26 @@ class Debug * @access public * @return string */ - public static function getThroughputRate() + public function getThroughputRate() { - return number_format(1 / self::getUseTime(), 2) . 'req/s'; + return number_format(1 / $this->getUseTime(), 2) . 'req/s'; } /** * 记录区间的内存使用情况 * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 * @return string */ - public static function getRangeMem($start, $end, $dec = 2) + public function getRangeMem($start, $end, $dec = 2) { - if (!isset(self::$mem['mem'][$end])) { - self::$mem['mem'][$end] = memory_get_usage(); + if (!isset($this->mem['mem'][$end])) { + $this->mem['mem'][$end] = memory_get_usage(); } - $size = self::$mem['mem'][$end] - self::$mem['mem'][$start]; + $size = $this->mem['mem'][$end] - $this->mem['mem'][$start]; $a = ['B', 'KB', 'MB', 'GB', 'TB']; $pos = 0; @@ -110,12 +141,12 @@ class Debug /** * 统计从开始到统计时的内存使用情况 * @access public - * @param integer $dec 小数位 + * @param integer|string $dec 小数位 * @return string */ - public static function getUseMem($dec = 2) + public function getUseMem($dec = 2) { - $size = memory_get_usage() - THINK_START_MEM; + $size = memory_get_usage() - $this->app->getBeginMem(); $a = ['B', 'KB', 'MB', 'GB', 'TB']; $pos = 0; @@ -130,18 +161,18 @@ class Debug /** * 统计区间的内存峰值情况 * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 * @return string */ - public static function getMemPeak($start, $end, $dec = 2) + public function getMemPeak($start, $end, $dec = 2) { - if (!isset(self::$mem['peak'][$end])) { - self::$mem['peak'][$end] = memory_get_peak_usage(); + if (!isset($this->mem['peak'][$end])) { + $this->mem['peak'][$end] = memory_get_peak_usage(); } - $size = self::$mem['peak'][$end] - self::$mem['peak'][$start]; + $size = $this->mem['peak'][$end] - $this->mem['peak'][$start]; $a = ['B', 'KB', 'MB', 'GB', 'TB']; $pos = 0; @@ -156,90 +187,77 @@ class Debug /** * 获取文件加载信息 * @access public - * @param bool $detail 是否显示详细 + * @param bool $detail 是否显示详细 * @return integer|array */ - public static function getFile($detail = false) + public function getFile($detail = false) { - $files = get_included_files(); - if ($detail) { - $info = []; + $files = get_included_files(); + $info = []; - foreach ($files as $file) { + foreach ($files as $key => $file) { $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; } return $info; } - return count($files); + return count(get_included_files()); } /** * 浏览器友好的变量输出 * @access public - * @param mixed $var 变量 - * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串) - * @param string|null $label 标签(默认为空) - * @param integer $flags htmlspecialchars 的标志 - * @return null|string + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @param integer $flags htmlspecialchars flags + * @return void|string */ - public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + public function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) { $label = (null === $label) ? '' : rtrim($label) . ':'; + if ($var instanceof Model || $var instanceof ModelCollection) { + $var = $var->toArray(); + } ob_start(); var_dump($var); - $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean()); - if (IS_CLI) { + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + + if (PHP_SAPI == 'cli') { $output = PHP_EOL . $label . $output . PHP_EOL; } else { if (!extension_loaded('xdebug')) { $output = htmlspecialchars($output, $flags); } - $output = '
    ' . $label . $output . '
    '; } - if ($echo) { echo($output); return; } - return $output; } - /** - * 调试信息注入到响应中 - * @access public - * @param Response $response 响应实例 - * @param string $content 返回的字符串 - * @return void - */ - public static function inject(Response $response, &$content) + public function inject(Response $response, &$content) { - $config = Config::get('trace'); + $config = $this->config; $type = isset($config['type']) ? $config['type'] : 'Html'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); unset($config['type']); - if (!class_exists($class)) { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - /** @var \think\debug\Console|\think\debug\Html $trace */ - $trace = new $class($config); + $trace = Loader::factory($type, '\\think\\debug\\', $config); if ($response instanceof Redirect) { - // TODO 记录 + //TODO 记录 } else { - $output = $trace->output($response, Log::getLog()); - + $output = $trace->output($response, $this->app['log']->getLog()); if (is_string($output)) { - // trace 调试信息注入 + // trace调试信息注入 $pos = strripos($content, ''); if (false !== $pos) { $content = substr($content, 0, $pos) . $output . substr($content, $pos); @@ -249,4 +267,12 @@ class Debug } } } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } } diff --git a/Server/thinkphp/library/think/Env.php b/Server/thinkphp/library/think/Env.php index 0a8b2509..eaeee943 100644 --- a/Server/thinkphp/library/think/Env.php +++ b/Server/thinkphp/library/think/Env.php @@ -13,27 +13,101 @@ namespace think; class Env { + /** + * 环境变量数据 + * @var array + */ + protected $data = []; + + public function __construct() + { + $this->data = $_ENV; + } + + /** + * 读取环境变量定义文件 + * @access public + * @param string $file 环境变量定义文件 + * @return void + */ + public function load($file) + { + $env = parse_ini_file($file, true); + $this->set($env); + } + /** * 获取环境变量值 * @access public - * @param string $name 环境变量名(支持二级 . 号分割) - * @param string $default 默认值 + * @param string $name 环境变量名 + * @param mixed $default 默认值 * @return mixed */ - public static function get($name, $default = null) + public function get($name = null, $default = null, $php_prefix = true) { - $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name))); - - if (false !== $result) { - if ('false' === $result) { - $result = false; - } elseif ('true' === $result) { - $result = true; - } - - return $result; + if (is_null($name)) { + return $this->data; } - return $default; + $name = strtoupper(str_replace('.', '_', $name)); + + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default, $php_prefix); + } + + protected function getEnv($name, $default = null, $php_prefix = true) + { + if ($php_prefix) { + $name = 'PHP_' . $name; + } + + $result = getenv($name); + + if (false === $result) { + return $default; + } + + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; + } + + /** + * 设置环境变量值 + * @access public + * @param string|array $env 环境变量 + * @param mixed $value 值 + * @return void + */ + public function set($env, $value = null) + { + if (is_array($env)) { + $env = array_change_key_case($env, CASE_UPPER); + + foreach ($env as $key => $val) { + if (is_array($val)) { + foreach ($val as $k => $v) { + $this->data[$key . '_' . strtoupper($k)] = $v; + } + } else { + $this->data[$key] = $val; + } + } + } else { + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; + } } } diff --git a/Server/thinkphp/library/think/Error.php b/Server/thinkphp/library/think/Error.php index 5f361d58..ea3328ee 100644 --- a/Server/thinkphp/library/think/Error.php +++ b/Server/thinkphp/library/think/Error.php @@ -18,6 +18,12 @@ use think\exception\ThrowableError; class Error { + /** + * 配置参数 + * @var array + */ + protected static $exceptionHandler; + /** * 注册异常处理 * @access public @@ -32,10 +38,9 @@ class Error } /** - * 异常处理 + * Exception Handler * @access public - * @param \Exception|\Throwable $e 异常 - * @return void + * @param \Exception|\Throwable $e */ public static function appException($e) { @@ -43,32 +48,29 @@ class Error $e = new ThrowableError($e); } - $handler = self::getExceptionHandler(); - $handler->report($e); + self::getExceptionHandler()->report($e); - if (IS_CLI) { - $handler->renderForConsole(new ConsoleOutput, $e); + if (PHP_SAPI == 'cli') { + self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); } else { - $handler->render($e)->send(); + self::getExceptionHandler()->render($e)->send(); } } /** - * 错误处理 + * Error Handler * @access public - * @param integer $errno 错误编号 - * @param integer $errstr 详细错误信息 - * @param string $errfile 出错的文件 - * @param integer $errline 出错行号 - * @return void + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 * @throws ErrorException */ public static function appError($errno, $errstr, $errfile = '', $errline = 0) { $exception = new ErrorException($errno, $errstr, $errfile, $errline); - - // 符合异常处理的则将错误信息托管至 think\exception\ErrorException if (error_reporting() & $errno) { + // 将错误信息托管至 think\exception\ErrorException throw $exception; } @@ -76,27 +78,27 @@ class Error } /** - * 异常中止处理 + * Shutdown Handler * @access public - * @return void */ public static function appShutdown() { - // 将错误信息托管至 think\ErrorException if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { - self::appException(new ErrorException( - $error['type'], $error['message'], $error['file'], $error['line'] - )); + // 将错误信息托管至think\ErrorException + $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']); + + self::appException($exception); } // 写入日志 - Log::save(); + Container::get('log')->save(); } /** * 确定错误类型是否致命 + * * @access protected - * @param int $type 错误类型 + * @param int $type * @return bool */ protected static function isFatal($type) @@ -105,7 +107,20 @@ class Error } /** - * 获取异常处理的实例 + * 设置异常处理类 + * + * @access public + * @param mixed $handle + * @return void + */ + public static function setExceptionHandler($handle) + { + self::$exceptionHandler = $handle; + } + + /** + * Get an instance of the exception handler. + * * @access public * @return Handle */ @@ -114,20 +129,16 @@ class Error static $handle; if (!$handle) { - // 异常处理 handle - $class = Config::get('exception_handle'); + // 异常处理handle + $class = self::$exceptionHandler; - if ($class && is_string($class) && class_exists($class) && - is_subclass_of($class, "\\think\\exception\\Handle") - ) { + if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { $handle = new $class; } else { $handle = new Handle; - if ($class instanceof \Closure) { $handle->setRender($class); } - } } diff --git a/Server/thinkphp/library/think/Exception.php b/Server/thinkphp/library/think/Exception.php index 1ef06bdb..414a090a 100644 --- a/Server/thinkphp/library/think/Exception.php +++ b/Server/thinkphp/library/think/Exception.php @@ -13,13 +13,15 @@ namespace think; class Exception extends \Exception { + /** - * @var array 保存异常页面显示的额外 Debug 数据 + * 保存异常页面显示的额外Debug数据 + * @var array */ protected $data = []; /** - * 设置异常额外的 Debug 数据 + * 设置异常额外的Debug数据 * 数据将会显示为下面的格式 * * Exception Data @@ -34,7 +36,6 @@ class Exception extends \Exception * @access protected * @param string $label 数据分类,用于异常页面显示 * @param array $data 需要显示的数据,必须为关联数组 - * @return void */ final protected function setData($label, array $data) { @@ -42,10 +43,10 @@ class Exception extends \Exception } /** - * 获取异常额外 Debug 数据 + * 获取异常额外Debug数据 * 主要用于输出到异常页面便于调试 * @access public - * @return array + * @return array 由setData设置的Debug数据 */ final public function getData() { diff --git a/Server/thinkphp/library/think/File.php b/Server/thinkphp/library/think/File.php index d2ed2208..b24b7770 100644 --- a/Server/thinkphp/library/think/File.php +++ b/Server/thinkphp/library/think/File.php @@ -16,61 +16,64 @@ use SplFileObject; class File extends SplFileObject { /** - * @var string 错误信息 + * 错误信息 + * @var string */ private $error = ''; /** - * @var string 当前完整文件名 + * 当前完整文件名 + * @var string */ protected $filename; /** - * @var string 上传文件名 + * 上传文件名 + * @var string */ protected $saveName; /** - * @var string 文件上传命名规则 + * 上传文件命名规则 + * @var string */ protected $rule = 'date'; /** - * @var array 文件上传验证规则 + * 上传文件验证规则 + * @var array */ protected $validate = []; /** - * @var bool 单元测试 + * 是否单元测试 + * @var bool */ protected $isTest; /** - * @var array 上传文件信息 + * 上传文件信息 + * @var array */ - protected $info; + protected $info = []; /** - * @var array 文件 hash 信息 + * 文件hash规则 + * @var array */ protected $hash = []; - /** - * File constructor. - * @access public - * @param string $filename 文件名称 - * @param string $mode 访问模式 - */ public function __construct($filename, $mode = 'r') { parent::__construct($filename, $mode); + $this->filename = $this->getRealPath() ?: $this->getPathname(); } /** - * 设置是否是单元测试 + * 是否测试 * @access public - * @param bool $test 是否是测试 + * @param bool $test 是否测试 * @return $this */ public function isTest($test = false) @@ -83,7 +86,7 @@ class File extends SplFileObject /** * 设置上传信息 * @access public - * @param array $info 上传文件信息 + * @param array $info 上传文件信息 * @return $this */ public function setUploadInfo($info) @@ -96,7 +99,7 @@ class File extends SplFileObject /** * 获取上传文件的信息 * @access public - * @param string $name 信息名称 + * @param string $name * @return array|string */ public function getInfo($name = '') @@ -117,7 +120,7 @@ class File extends SplFileObject /** * 设置上传文件的保存文件名 * @access public - * @param string $saveName 保存名称 + * @param string $saveName * @return $this */ public function setSaveName($saveName) @@ -130,7 +133,7 @@ class File extends SplFileObject /** * 获取文件的哈希散列值 * @access public - * @param string $type 类型 + * @param string $type * @return string */ public function hash($type = 'sha1') @@ -145,17 +148,20 @@ class File extends SplFileObject /** * 检查目录是否可写 * @access protected - * @param string $path 目录 + * @param string $path 目录 * @return boolean */ protected function checkPath($path) { - if (is_dir($path) || mkdir($path, 0755, true)) { + if (is_dir($path)) { + return true; + } + + if (mkdir($path, 0755, true)) { return true; } $this->error = ['directory {:path} creation failed', ['path' => $path]]; - return false; } @@ -174,7 +180,7 @@ class File extends SplFileObject /** * 设置文件的命名规则 * @access public - * @param string $rule 文件命名规则 + * @param string $rule 文件命名规则 * @return $this */ public function rule($rule) @@ -187,10 +193,10 @@ class File extends SplFileObject /** * 设置上传文件的验证规则 * @access public - * @param array $rule 验证规则 + * @param array $rule 验证规则 * @return $this */ - public function validate(array $rule = []) + public function validate($rule = []) { $this->validate = $rule; @@ -204,40 +210,27 @@ class File extends SplFileObject */ public function isValid() { - return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename); + if ($this->isTest) { + return is_file($this->filename); + } + + return is_uploaded_file($this->filename); } /** * 检测上传文件 * @access public - * @param array $rule 验证规则 + * @param array $rule 验证规则 * @return bool */ public function check($rule = []) { $rule = $rule ?: $this->validate; - /* 检查文件大小 */ - if (isset($rule['size']) && !$this->checkSize($rule['size'])) { - $this->error = 'filesize not match'; - return false; - } - - /* 检查文件 Mime 类型 */ - if (isset($rule['type']) && !$this->checkMime($rule['type'])) { - $this->error = 'mimetype to upload is not allowed'; - return false; - } - - /* 检查文件后缀 */ - if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) { - $this->error = 'extensions to upload is not allowed'; - return false; - } - - /* 检查图像文件 */ - if (!$this->checkImg()) { - $this->error = 'illegal image files'; + if ((isset($rule['size']) && !$this->checkSize($rule['size'])) + || (isset($rule['type']) && !$this->checkMime($rule['type'])) + || (isset($rule['ext']) && !$this->checkExt($rule['ext'])) + || !$this->checkImg()) { return false; } @@ -247,7 +240,7 @@ class File extends SplFileObject /** * 检测上传文件后缀 * @access public - * @param array|string $ext 允许后缀 + * @param array|string $ext 允许后缀 * @return bool */ public function checkExt($ext) @@ -258,7 +251,12 @@ class File extends SplFileObject $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); - return in_array($extension, $ext); + if (!in_array($extension, $ext)) { + $this->error = 'extensions to upload is not allowed'; + return false; + } + + return true; } /** @@ -270,16 +268,16 @@ class File extends SplFileObject { $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); - // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true - return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]); + /* 对图像文件进行严格检测 */ + if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) { + $this->error = 'illegal image files'; + return false; + } + + return true; } - /** - * 判断图像类型 - * @access protected - * @param string $image 图片名称 - * @return bool|int - */ + // 判断图像类型 protected function getImageType($image) { if (function_exists('exif_imagetype')) { @@ -297,36 +295,49 @@ class File extends SplFileObject /** * 检测上传文件大小 * @access public - * @param integer $size 最大大小 + * @param integer $size 最大大小 * @return bool */ public function checkSize($size) { - return $this->getSize() <= $size; + if ($this->getSize() > (int) $size) { + $this->error = 'filesize not match'; + return false; + } + + return true; } /** * 检测上传文件类型 * @access public - * @param array|string $mime 允许类型 + * @param array|string $mime 允许类型 * @return bool */ public function checkMime($mime) { - $mime = is_string($mime) ? explode(',', $mime) : $mime; + if (is_string($mime)) { + $mime = explode(',', $mime); + } - return in_array(strtolower($this->getMime()), $mime); + if (!in_array(strtolower($this->getMime()), $mime)) { + $this->error = 'mimetype to upload is not allowed'; + return false; + } + + return true; } /** * 移动文件 * @access public - * @param string $path 保存路径 - * @param string|bool $savename 保存的文件名 默认自动生成 - * @param boolean $replace 同名文件是否覆盖 - * @return false|File + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @param bool $autoAppendExt 自动补充扩展名 + * @return false|File false-失败 否则返回File实例 */ - public function move($path, $savename = true, $replace = true) + public function move($path, $savename = true, $replace = true, $autoAppendExt = true) { // 文件上传失败,捕获错误代码 if (!empty($this->info['error'])) { @@ -345,9 +356,9 @@ class File extends SplFileObject return false; } - $path = rtrim($path, DS) . DS; + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; // 文件保存命名规则 - $saveName = $this->buildSaveName($savename); + $saveName = $this->buildSaveName($savename, $autoAppendExt); $filename = $path . $saveName; // 检测目录 @@ -355,7 +366,7 @@ class File extends SplFileObject return false; } - // 不覆盖同名文件 + /* 不覆盖同名文件 */ if (!$replace && is_file($filename)) { $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; return false; @@ -369,9 +380,10 @@ class File extends SplFileObject return false; } - // 返回 File 对象实例 + // 返回 File对象实例 $file = new self($filename); - $file->setSaveName($saveName)->setUploadInfo($this->info); + $file->setSaveName($saveName); + $file->setUploadInfo($this->info); return $file; } @@ -379,47 +391,60 @@ class File extends SplFileObject /** * 获取保存文件名 * @access protected - * @param string|bool $savename 保存的文件名 默认自动生成 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param bool $autoAppendExt 自动补充扩展名 * @return string */ - protected function buildSaveName($savename) + protected function buildSaveName($savename, $autoAppendExt = true) { - // 自动生成文件名 if (true === $savename) { - if ($this->rule instanceof \Closure) { - $savename = call_user_func_array($this->rule, [$this]); - } else { - switch ($this->rule) { - case 'date': - $savename = date('Ymd') . DS . md5(microtime(true)); - break; - default: - if (in_array($this->rule, hash_algos())) { - $hash = $this->hash($this->rule); - $savename = substr($hash, 0, 2) . DS . substr($hash, 2); - } elseif (is_callable($this->rule)) { - $savename = call_user_func($this->rule); - } else { - $savename = date('Ymd') . DS . md5(microtime(true)); - } - } - } + // 自动生成文件名 + $savename = $this->autoBuildName(); } elseif ('' === $savename || false === $savename) { + // 保留原文件名 $savename = $this->getInfo('name'); } - if (!strpos($savename, '.')) { + if ($autoAppendExt && false === strpos($savename, '.')) { $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); } return $savename; } + /** + * 自动生成文件名 + * @access protected + * @return string + */ + protected function autoBuildName() + { + if ($this->rule instanceof \Closure) { + $savename = call_user_func_array($this->rule, [$this]); + } else { + switch ($this->rule) { + case 'date': + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); + } + } + } + + return $savename; + } + /** * 获取错误代码信息 * @access private - * @param int $errorNo 错误号 - * @return $this + * @param int $errorNo 错误号 */ private function error($errorNo) { @@ -443,8 +468,6 @@ class File extends SplFileObject default: $this->error = 'unknown upload error'; } - - return $this; } /** @@ -454,6 +477,8 @@ class File extends SplFileObject */ public function getError() { + $lang = Container::get('lang'); + if (is_array($this->error)) { list($msg, $vars) = $this->error; } else { @@ -461,16 +486,9 @@ class File extends SplFileObject $vars = []; } - return Lang::has($msg) ? Lang::get($msg, $vars) : $msg; + return $lang->has($msg) ? $lang->get($msg, $vars) : $msg; } - /** - * 魔法方法,获取文件的 hash 值 - * @access public - * @param string $method 方法名 - * @param mixed $args 调用参数 - * @return string - */ public function __call($method, $args) { return $this->hash($method); diff --git a/Server/thinkphp/library/think/Hook.php b/Server/thinkphp/library/think/Hook.php index a69ce546..1d011410 100644 --- a/Server/thinkphp/library/think/Hook.php +++ b/Server/thinkphp/library/think/Hook.php @@ -14,68 +14,122 @@ namespace think; class Hook { /** - * @var array 标签 + * 钩子行为定义 + * @var array */ - private static $tags = []; + private $tags = []; + + /** + * 绑定行为列表 + * @var array + */ + protected $bind = []; + + /** + * 入口方法名称 + * @var string + */ + private static $portal = 'run'; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 指定入口方法名称 + * @access public + * @param string $name 方法名 + * @return $this + */ + public function portal($name) + { + self::$portal = $name; + return $this; + } + + /** + * 指定行为标识 便于调用 + * @access public + * @param string|array $name 行为标识 + * @param mixed $behavior 行为 + * @return $this + */ + public function alias($name, $behavior = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $behavior; + } + + return $this; + } /** * 动态添加行为扩展到某个标签 * @access public - * @param string $tag 标签名称 - * @param mixed $behavior 行为名称 - * @param bool $first 是否放到开头执行 + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 * @return void */ - public static function add($tag, $behavior, $first = false) + public function add($tag, $behavior, $first = false) { - isset(self::$tags[$tag]) || self::$tags[$tag] = []; + isset($this->tags[$tag]) || $this->tags[$tag] = []; if (is_array($behavior) && !is_callable($behavior)) { - if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) { - unset($behavior['_overlay']); - self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior); + if (!array_key_exists('_overlay', $behavior)) { + $this->tags[$tag] = array_merge($this->tags[$tag], $behavior); } else { unset($behavior['_overlay']); - self::$tags[$tag] = $behavior; + $this->tags[$tag] = $behavior; } } elseif ($first) { - array_unshift(self::$tags[$tag], $behavior); + array_unshift($this->tags[$tag], $behavior); } else { - self::$tags[$tag][] = $behavior; + $this->tags[$tag][] = $behavior; } } /** * 批量导入插件 * @access public - * @param array $tags 插件信息 - * @param boolean $recursive 是否递归合并 + * @param array $tags 插件信息 + * @param bool $recursive 是否递归合并 * @return void */ - public static function import(array $tags, $recursive = true) + public function import(array $tags, $recursive = true) { if ($recursive) { foreach ($tags as $tag => $behavior) { - self::add($tag, $behavior); + $this->add($tag, $behavior); } } else { - self::$tags = $tags + self::$tags; + $this->tags = $tags + $this->tags; } } /** * 获取插件信息 * @access public - * @param string $tag 插件位置(留空获取全部) + * @param string $tag 插件位置 留空获取全部 * @return array */ - public static function get($tag = '') + public function get($tag = '') { if (empty($tag)) { - return self::$tags; + //获取全部的插件信息 + return $this->tags; } - return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; + return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : []; } /** @@ -83,18 +137,17 @@ class Hook * @access public * @param string $tag 标签名称 * @param mixed $params 传入参数 - * @param mixed $extra 额外参数 * @param bool $once 只获取一个有效返回值 * @return mixed */ - public static function listen($tag, &$params = null, $extra = null, $once = false) + public function listen($tag, $params = null, $once = false) { $results = []; + $tags = $this->get($tag); - foreach (static::get($tag) as $key => $name) { - $results[$key] = self::exec($name, $tag, $params, $extra); + foreach ($tags as $key => $name) { + $results[$key] = $this->execTag($name, $tag, $params); - // 如果返回 false,或者仅获取一个有效返回则中断行为执行 if (false === $results[$key] || (!is_null($results[$key]) && $once)) { break; } @@ -104,45 +157,64 @@ class Hook } /** - * 执行某个行为 + * 执行行为 * @access public - * @param mixed $class 要执行的行为 - * @param string $tag 方法名(标签名) - * @param mixed $params 传人的参数 - * @param mixed $extra 额外参数 + * @param mixed $class 行为 + * @param mixed $params 参数 * @return mixed */ - public static function exec($class, $tag = '', &$params = null, $extra = null) + public function exec($class, $params = null) { - App::$debug && Debug::remark('behavior_start', 'time'); + if ($class instanceof \Closure || is_array($class)) { + $method = $class; + } else { + if (isset($this->bind[$class])) { + $class = $this->bind[$class]; + } + $method = [$class, self::$portal]; + } + return $this->app->invoke($method, [$params]); + } + + /** + * 执行某个标签的行为 + * @access protected + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 参数 + * @return mixed + */ + protected function execTag($class, $tag = '', $params = null) + { $method = Loader::parseName($tag, 1, false); if ($class instanceof \Closure) { - $result = call_user_func_array($class, [ & $params, $extra]); - $class = 'Closure'; - } elseif (is_array($class)) { - list($class, $method) = $class; - - $result = (new $class())->$method($params, $extra); - $class = $class . '->' . $method; - } elseif (is_object($class)) { - $result = $class->$method($params, $extra); - $class = get_class($class); - } elseif (strpos($class, '::')) { - $result = call_user_func_array($class, [ & $params, $extra]); + $call = $class; + $class = 'Closure'; + } elseif (is_array($class) || strpos($class, '::')) { + $call = $class; } else { - $obj = new $class(); - $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; - $result = $obj->$method($params, $extra); + $obj = Container::get($class); + + if (!is_callable([$obj, $method])) { + $method = self::$portal; + } + + $call = [$class, $method]; + $class = $class . '->' . $method; } - if (App::$debug) { - Debug::remark('behavior_end', 'time'); - Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); - } + $result = $this->app->invoke($call, [$params]); return $result; } + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } } diff --git a/Server/thinkphp/library/think/Lang.php b/Server/thinkphp/library/think/Lang.php index a7ac042c..ed36dd8c 100644 --- a/Server/thinkphp/library/think/Lang.php +++ b/Server/thinkphp/library/think/Lang.php @@ -14,92 +14,104 @@ namespace think; class Lang { /** - * @var array 语言数据 + * 多语言信息 + * @var array */ - private static $lang = []; + private $lang = []; /** - * @var string 语言作用域 + * 当前语言 + * @var string */ - private static $range = 'zh-cn'; + private $range = 'zh-cn'; /** - * @var string 语言自动侦测的变量 + * 多语言自动侦测变量名 + * @var string */ - protected static $langDetectVar = 'lang'; + protected $langDetectVar = 'lang'; /** - * @var string 语言 Cookie 变量 + * 多语言cookie变量 + * @var string */ - protected static $langCookieVar = 'think_var'; + protected $langCookieVar = 'think_var'; /** - * @var int 语言 Cookie 的过期时间 + * 允许的多语言列表 + * @var array */ - protected static $langCookieExpire = 3600; + protected $allowLangList = []; /** - * @var array 允许语言列表 + * Accept-Language转义为对应语言包名称 系统默认配置 + * @var string */ - protected static $allowLangList = []; + protected $acceptLanguage = [ + 'zh-hans-cn' => 'zh-cn', + ]; /** - * @var array Accept-Language 转义为对应语言包名称 系统默认配置 + * 应用对象 + * @var App */ - protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn']; + protected $app; - /** - * 设定当前的语言 - * @access public - * @param string $range 语言作用域 - * @return string - */ - public static function range($range = '') + public function __construct(App $app) { - if ($range) { - self::$range = $range; - } + $this->app = $app; + } - return self::$range; + // 设定当前的语言 + public function range($range = '') + { + if ('' == $range) { + return $this->range; + } else { + $this->range = $range; + } } /** * 设置语言定义(不区分大小写) * @access public - * @param string|array $name 语言变量 + * @param string|array $name 语言变量 * @param string $value 语言值 * @param string $range 语言作用域 * @return mixed */ - public static function set($name, $value = null, $range = '') + public function set($name, $value = null, $range = '') { - $range = $range ?: self::$range; - - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; + $range = $range ?: $this->range; + // 批量定义 + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; } if (is_array($name)) { - return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; + return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range]; } - return self::$lang[$range][strtolower($name)] = $value; + return $this->lang[$range][strtolower($name)] = $value; } /** * 加载语言定义(不区分大小写) * @access public - * @param array|string $file 语言文件 - * @param string $range 语言作用域 - * @return mixed + * @param string|array $file 语言文件 + * @param string $range 语言作用域 + * @return array */ - public static function load($file, $range = '') + public function load($file, $range = '') { - $range = $range ?: self::$range; - $file = is_string($file) ? [$file] : $file; + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; + // 批量定义 + if (is_string($file)) { + $file = [$file]; } $lang = []; @@ -107,10 +119,8 @@ class Lang foreach ($file as $_file) { if (is_file($_file)) { // 记录加载信息 - App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); - + $this->app->log('[ LANG ] ' . $_file); $_lang = include $_file; - if (is_array($_lang)) { $lang = array_change_key_case($_lang) + $lang; } @@ -118,45 +128,45 @@ class Lang } if (!empty($lang)) { - self::$lang[$range] = $lang + self::$lang[$range]; + $this->lang[$range] = $lang + $this->lang[$range]; } - return self::$lang[$range]; + return $this->lang[$range]; } /** * 获取语言定义(不区分大小写) * @access public - * @param string|null $name 语言变量 - * @param string $range 语言作用域 - * @return mixed + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return bool */ - public static function has($name, $range = '') + public function has($name, $range = '') { - $range = $range ?: self::$range; + $range = $range ?: $this->range; - return isset(self::$lang[$range][strtolower($name)]); + return isset($this->lang[$range][strtolower($name)]); } /** * 获取语言定义(不区分大小写) * @access public - * @param string|null $name 语言变量 - * @param array $vars 变量替换 - * @param string $range 语言作用域 + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 * @return mixed */ - public static function get($name = null, $vars = [], $range = '') + public function get($name = null, $vars = [], $range = '') { - $range = $range ?: self::$range; + $range = $range ?: $this->range; // 空参数返回所有定义 - if (empty($name)) { - return self::$lang[$range]; + if (is_null($name)) { + return $this->lang[$range]; } $key = strtolower($name); - $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $name; + $value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name; // 变量解析 if (!empty($vars) && is_array($vars)) { @@ -177,7 +187,6 @@ class Lang } $value = str_replace($replace, $vars, $value); } - } return $value; @@ -188,41 +197,51 @@ class Lang * @access public * @return string */ - public static function detect() + public function detect() { + // 自动侦测设置获取语言选择 $langSet = ''; - if (isset($_GET[self::$langDetectVar])) { - // url 中设置了语言变量 - $langSet = strtolower($_GET[self::$langDetectVar]); - } elseif (isset($_COOKIE[self::$langCookieVar])) { - // Cookie 中设置了语言变量 - $langSet = strtolower($_COOKIE[self::$langCookieVar]); + if (isset($_GET[$this->langDetectVar])) { + // url中设置了语言变量 + $langSet = strtolower($_GET[$this->langDetectVar]); + } elseif (isset($_COOKIE[$this->langCookieVar])) { + // Cookie中设置了语言变量 + $langSet = strtolower($_COOKIE[$this->langCookieVar]); } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // 自动侦测浏览器语言 preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); - $langSet = strtolower($matches[1]); - $acceptLangs = Config::get('header_accept_lang'); - - if (isset($acceptLangs[$langSet])) { - $langSet = $acceptLangs[$langSet]; - } elseif (isset(self::$acceptLanguage[$langSet])) { - $langSet = self::$acceptLanguage[$langSet]; + $langSet = strtolower($matches[1]); + if (isset($this->acceptLanguage[$langSet])) { + $langSet = $this->acceptLanguage[$langSet]; } } if (preg_match('/^([a-z\d\-]+)/i', $langSet, $matches)) { $langSet = strtolower($matches[1]); } else { - $langSet = self::$range; + $langSet = $this->range; } - // 合法的语言 - if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { - self::$range = $langSet ?: self::$range; + if (empty($this->allowLangList) || in_array($langSet, $this->allowLangList)) { + // 合法的语言 + $this->range = $langSet ?: $this->range; } - return self::$range; + return $this->range; + } + + /** + * 设置当前语言到Cookie + * @access public + * @param string $lang 语言 + * @return void + */ + public function saveToCookie($lang = null) + { + $range = $lang ?: $this->range; + + $_COOKIE[$this->langCookieVar] = $range; } /** @@ -231,31 +250,20 @@ class Lang * @param string $var 变量名称 * @return void */ - public static function setLangDetectVar($var) + public function setLangDetectVar($var) { - self::$langDetectVar = $var; + $this->langDetectVar = $var; } /** - * 设置语言的 cookie 保存变量 + * 设置语言的cookie保存变量 * @access public * @param string $var 变量名称 * @return void */ - public static function setLangCookieVar($var) + public function setLangCookieVar($var) { - self::$langCookieVar = $var; - } - - /** - * 设置语言的 cookie 的过期时间 - * @access public - * @param string $expire 过期时间 - * @return void - */ - public static function setLangCookieExpire($expire) - { - self::$langCookieExpire = $expire; + $this->langCookieVar = $var; } /** @@ -264,8 +272,19 @@ class Lang * @param array $list 语言列表 * @return void */ - public static function setAllowLangList($list) + public function setAllowLangList(array $list) { - self::$allowLangList = $list; + $this->allowLangList = $list; + } + + /** + * 设置转义的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public function setAcceptLanguage(array $list) + { + $this->acceptLanguage = array_merge($this->acceptLanguage, $list); } } diff --git a/Server/thinkphp/library/think/Loader.php b/Server/thinkphp/library/think/Loader.php index d813a5d7..d807db64 100644 --- a/Server/thinkphp/library/think/Loader.php +++ b/Server/thinkphp/library/think/Loader.php @@ -16,278 +16,76 @@ use think\exception\ClassNotFoundException; class Loader { /** - * @var array 实例数组 - */ - protected static $instance = []; - - /** - * @var array 类名映射 + * 类名映射信息 + * @var array */ protected static $classMap = []; /** - * @var array 命名空间别名 + * 类库别名 + * @var array */ - protected static $namespaceAlias = []; + protected static $classAlias = []; /** - * @var array PSR-4 命名空间前缀长度映射 + * PSR-4 + * @var array */ private static $prefixLengthsPsr4 = []; + private static $prefixDirsPsr4 = []; + private static $fallbackDirsPsr4 = []; /** - * @var array PSR-4 的加载目录 - */ - private static $prefixDirsPsr4 = []; - - /** - * @var array PSR-4 加载失败的回退目录 - */ - private static $fallbackDirsPsr4 = []; - - /** - * @var array PSR-0 命名空间前缀映射 - */ - private static $prefixesPsr0 = []; - - /** - * @var array PSR-0 加载失败的回退目录 + * PSR-0 + * @var array */ + private static $prefixesPsr0 = []; private static $fallbackDirsPsr0 = []; /** - * @var array 需要加载的文件 + * 需要加载的文件 + * @var array */ private static $files = []; /** - * 自动加载 - * @access public - * @param string $class 类名 - * @return bool + * Composer安装路径 + * @var string */ - public static function autoload($class) + private static $composerPath; + + // 获取应用根目录 + public static function getRootPath() { - // 检测命名空间别名 - if (!empty(self::$namespaceAlias)) { - $namespace = dirname($class); - if (isset(self::$namespaceAlias[$namespace])) { - $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); - if (class_exists($original)) { - return class_alias($original, $class, false); - } - } - } - - if ($file = self::findFile($class)) { - // 非 Win 环境不严格区分大小写 - if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) { - __include_file($file); - return true; - } - } - - return false; - } - - /** - * 查找文件 - * @access private - * @param string $class 类名 - * @return bool|string - */ - private static function findFile($class) - { - // 类库映射 - if (!empty(self::$classMap[$class])) { - return self::$classMap[$class]; - } - - // 查找 PSR-4 - $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; - $first = $class[0]; - - if (isset(self::$prefixLengthsPsr4[$first])) { - foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { - if (0 === strpos($class, $prefix)) { - foreach (self::$prefixDirsPsr4[$prefix] as $dir) { - if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) { - return $file; - } - } - } - } - } - - // 查找 PSR-4 fallback dirs - foreach (self::$fallbackDirsPsr4 as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr4)) { - return $file; - } - } - - // 查找 PSR-0 - if (false !== $pos = strrpos($class, '\\')) { - // namespace class name - $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS); + if ('cli' == PHP_SAPI) { + $scriptName = realpath($_SERVER['argv'][0]); } else { - // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DS) . EXT; + $scriptName = $_SERVER['SCRIPT_FILENAME']; } - if (isset(self::$prefixesPsr0[$first])) { - foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { - if (0 === strpos($class, $prefix)) { - foreach ($dirs as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr0)) { - return $file; - } - } - } - } + $path = realpath(dirname($scriptName)); + + if (!is_file($path . DIRECTORY_SEPARATOR . 'think')) { + $path = dirname($path); } - // 查找 PSR-0 fallback dirs - foreach (self::$fallbackDirsPsr0 as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr0)) { - return $file; - } - } - - // 找不到则设置映射为 false 并返回 - return self::$classMap[$class] = false; + return $path . DIRECTORY_SEPARATOR; } - /** - * 注册 classmap - * @access public - * @param string|array $class 类名 - * @param string $map 映射 - * @return void - */ - public static function addClassMap($class, $map = '') - { - if (is_array($class)) { - self::$classMap = array_merge(self::$classMap, $class); - } else { - self::$classMap[$class] = $map; - } - } - - /** - * 注册命名空间 - * @access public - * @param string|array $namespace 命名空间 - * @param string $path 路径 - * @return void - */ - public static function addNamespace($namespace, $path = '') - { - if (is_array($namespace)) { - foreach ($namespace as $prefix => $paths) { - self::addPsr4($prefix . '\\', rtrim($paths, DS), true); - } - } else { - self::addPsr4($namespace . '\\', rtrim($path, DS), true); - } - } - - /** - * 添加 PSR-0 命名空间 - * @access private - * @param array|string $prefix 空间前缀 - * @param array $paths 路径 - * @param bool $prepend 预先设置的优先级更高 - * @return void - */ - private static function addPsr0($prefix, $paths, $prepend = false) - { - if (!$prefix) { - self::$fallbackDirsPsr0 = $prepend ? - array_merge((array) $paths, self::$fallbackDirsPsr0) : - array_merge(self::$fallbackDirsPsr0, (array) $paths); - } else { - $first = $prefix[0]; - - if (!isset(self::$prefixesPsr0[$first][$prefix])) { - self::$prefixesPsr0[$first][$prefix] = (array) $paths; - } else { - self::$prefixesPsr0[$first][$prefix] = $prepend ? - array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) : - array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths); - } - } - } - - /** - * 添加 PSR-4 空间 - * @access private - * @param array|string $prefix 空间前缀 - * @param string $paths 路径 - * @param bool $prepend 预先设置的优先级更高 - * @return void - */ - private static function addPsr4($prefix, $paths, $prepend = false) - { - if (!$prefix) { - // Register directories for the root namespace. - self::$fallbackDirsPsr4 = $prepend ? - array_merge((array) $paths, self::$fallbackDirsPsr4) : - array_merge(self::$fallbackDirsPsr4, (array) $paths); - - } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { - // Register directories for a new namespace. - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException( - "A non-empty PSR-4 prefix must end with a namespace separator." - ); - } - - self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - self::$prefixDirsPsr4[$prefix] = (array) $paths; - - } else { - self::$prefixDirsPsr4[$prefix] = $prepend ? - // Prepend directories for an already registered namespace. - array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) : - // Append directories for an already registered namespace. - array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths); - } - } - - /** - * 注册命名空间别名 - * @access public - * @param array|string $namespace 命名空间 - * @param string $original 源文件 - * @return void - */ - public static function addNamespaceAlias($namespace, $original = '') - { - if (is_array($namespace)) { - self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace); - } else { - self::$namespaceAlias[$namespace] = $original; - } - } - - /** - * 注册自动加载机制 - * @access public - * @param callable $autoload 自动加载处理方法 - * @return void - */ - public static function register($autoload = null) + // 注册自动加载机制 + public static function register($autoload = '') { // 注册系统自动加载 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); - // Composer 自动加载支持 - if (is_dir(VENDOR_PATH . 'composer')) { - if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) { - require VENDOR_PATH . 'composer' . DS . 'autoload_static.php'; + $rootPath = self::getRootPath(); + + self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; + + // Composer自动加载支持 + if (is_dir(self::$composerPath)) { + if (is_file(self::$composerPath . 'autoload_static.php')) { + require self::$composerPath . 'autoload_static.php'; $declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass); @@ -298,58 +96,255 @@ class Loader } } } else { - self::registerComposerLoader(); + self::registerComposerLoader(self::$composerPath); } } // 注册命名空间定义 self::addNamespace([ - 'think' => LIB_PATH . 'think' . DS, - 'behavior' => LIB_PATH . 'behavior' . DS, - 'traits' => LIB_PATH . 'traits' . DS, + 'think' => __DIR__, + 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', ]); // 加载类库映射文件 - if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { - self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); + if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) { + self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')); } - self::loadComposerAutoloadFiles(); + // 自动加载extend目录 + self::addAutoLoadDir($rootPath . 'extend'); + } - // 自动加载 extend 目录 - self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); + // 自动加载 + public static function autoload($class) + { + if (isset(self::$classAlias[$class])) { + return class_alias(self::$classAlias[$class], $class); + } + + if ($file = self::findFile($class)) { + + // Win环境严格区分大小写 + if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) { + return false; + } + + __include_file($file); + return true; + } } /** - * 注册 composer 自动加载 + * 查找文件 * @access private - * @return void + * @param string $class + * @return string|false */ - private static function registerComposerLoader() + private static function findFile($class) { - if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) { - $map = require VENDOR_PATH . 'composer/autoload_namespaces.php'; + if (!empty(self::$classMap[$class])) { + // 类库映射 + return self::$classMap[$class]; + } + + // 查找 PSR-4 + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + + $first = $class[0]; + if (isset(self::$prefixLengthsPsr4[$first])) { + foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach (self::$prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // 查找 PSR-4 fallback dirs + foreach (self::$fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // 查找 PSR-0 + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; + } + + if (isset(self::$prefixesPsr0[$first])) { + foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // 查找 PSR-0 fallback dirs + foreach (self::$fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + return self::$classMap[$class] = false; + } + + // 注册classmap + public static function addClassMap($class, $map = '') + { + if (is_array($class)) { + self::$classMap = array_merge(self::$classMap, $class); + } else { + self::$classMap[$class] = $map; + } + } + + // 注册命名空间 + public static function addNamespace($namespace, $path = '') + { + if (is_array($namespace)) { + foreach ($namespace as $prefix => $paths) { + self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true); + } + } else { + self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true); + } + } + + // 添加Ps0空间 + private static function addPsr0($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + self::$fallbackDirsPsr0 = array_merge( + (array) $paths, + self::$fallbackDirsPsr0 + ); + } else { + self::$fallbackDirsPsr0 = array_merge( + self::$fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + + if ($prepend) { + self::$prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + self::$prefixesPsr0[$first][$prefix] + ); + } else { + self::$prefixesPsr0[$first][$prefix] = array_merge( + self::$prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + // 添加Psr4空间 + private static function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + self::$fallbackDirsPsr4 = array_merge( + (array) $paths, + self::$fallbackDirsPsr4 + ); + } else { + self::$fallbackDirsPsr4 = array_merge( + self::$fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + + self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + self::$prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + self::$prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + self::$prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + self::$prefixDirsPsr4[$prefix] = array_merge( + self::$prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + // 注册自动加载类库目录 + public static function addAutoLoadDir($path) + { + self::$fallbackDirsPsr4[] = $path; + } + + // 注册类别名 + public static function addClassAlias($alias, $class = null) + { + if (is_array($alias)) { + self::$classAlias = array_merge(self::$classAlias, $alias); + } else { + self::$classAlias[$alias] = $class; + } + } + + // 注册composer自动加载 + public static function registerComposerLoader($composerPath) + { + if (is_file($composerPath . 'autoload_namespaces.php')) { + $map = require $composerPath . 'autoload_namespaces.php'; foreach ($map as $namespace => $path) { self::addPsr0($namespace, $path); } } - if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) { - $map = require VENDOR_PATH . 'composer/autoload_psr4.php'; + if (is_file($composerPath . 'autoload_psr4.php')) { + $map = require $composerPath . 'autoload_psr4.php'; foreach ($map as $namespace => $path) { self::addPsr4($namespace, $path); } } - if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) { - $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php'; + if (is_file($composerPath . 'autoload_classmap.php')) { + $classMap = require $composerPath . 'autoload_classmap.php'; if ($classMap) { self::addClassMap($classMap); } } - if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) { - self::$files = require VENDOR_PATH . 'composer/autoload_files.php'; + if (is_file($composerPath . 'autoload_files.php')) { + self::$files = require $composerPath . 'autoload_files.php'; } } @@ -365,246 +360,12 @@ class Loader } } - /** - * 导入所需的类库 同 Java 的 Import 本函数有缓存功能 - * @access public - * @param string $class 类库命名空间字符串 - * @param string $baseUrl 起始路径 - * @param string $ext 导入的文件扩展名 - * @return bool - */ - public static function import($class, $baseUrl = '', $ext = EXT) - { - static $_file = []; - $key = $class . $baseUrl; - $class = str_replace(['.', '#'], [DS, '.'], $class); - - if (isset($_file[$key])) { - return true; - } - - if (empty($baseUrl)) { - list($name, $class) = explode(DS, $class, 2); - - if (isset(self::$prefixDirsPsr4[$name . '\\'])) { - // 注册的命名空间 - $baseUrl = self::$prefixDirsPsr4[$name . '\\']; - } elseif ('@' == $name) { - // 加载当前模块应用类库 - $baseUrl = App::$modulePath; - } elseif (is_dir(EXTEND_PATH . $name)) { - $baseUrl = EXTEND_PATH . $name . DS; - } else { - // 加载其它模块的类库 - $baseUrl = APP_PATH . $name . DS; - } - } elseif (substr($baseUrl, -1) != DS) { - $baseUrl .= DS; - } - - // 如果类存在则导入类库文件 - if (is_array($baseUrl)) { - foreach ($baseUrl as $path) { - if (is_file($filename = $path . DS . $class . $ext)) { - break; - } - } - } else { - $filename = $baseUrl . $class . $ext; - } - - if (!empty($filename) && - is_file($filename) && - (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME)) - ) { - __include_file($filename); - $_file[$key] = true; - - return true; - } - - return false; - } - - /** - * 实例化(分层)模型 - * @access public - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object - * @throws ClassNotFoundException - */ - public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') - { - $uid = $name . $layer; - - if (isset(self::$instance[$uid])) { - return self::$instance[$uid]; - } - - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $model = new $class(); - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - - if (class_exists($class)) { - $model = new $class(); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - return self::$instance[$uid] = $model; - } - - /** - * 实例化(分层)控制器 格式:[模块名/]控制器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $empty 空控制器名称 - * @return object - * @throws ClassNotFoundException - */ - public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') - { - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - return App::invokeClass($class); - } - - if ($empty) { - $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix); - - if (class_exists($emptyClass)) { - return new $emptyClass(Request::instance()); - } - } - - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - /** - * 实例化验证类 格式:[模块名/]验证器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object|false - * @throws ClassNotFoundException - */ - public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') - { - $name = $name ?: Config::get('default_validate'); - - if (empty($name)) { - return new Validate; - } - - $uid = $name . $layer; - if (isset(self::$instance[$uid])) { - return self::$instance[$uid]; - } - - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $validate = new $class; - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - - if (class_exists($class)) { - $validate = new $class; - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - return self::$instance[$uid] = $validate; - } - - /** - * 解析模块和类名 - * @access protected - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return array - */ - protected static function getModuleAndClass($name, $layer, $appendSuffix) - { - if (false !== strpos($name, '\\')) { - $module = Request::instance()->module(); - $class = $name; - } else { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); - } else { - $module = Request::instance()->module(); - } - - $class = self::parseClass($module, $layer, $name, $appendSuffix); - } - - return [$module, $class]; - } - - /** - * 数据库初始化 并取得数据库类实例 - * @access public - * @param mixed $config 数据库配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Connection - */ - public static function db($config = [], $name = false) - { - return Db::connect($config, $name); - } - - /** - * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 - * @access public - * @param string $url 调用地址 - * @param string|array $vars 调用参数 支持字符串和数组 - * @param string $layer 要调用的控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return mixed - */ - public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) - { - $info = pathinfo($url); - $action = $info['basename']; - $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller(); - $class = self::controller($module, $layer, $appendSuffix); - - if ($class) { - if (is_scalar($vars)) { - if (strpos($vars, '=')) { - parse_str($vars, $vars); - } else { - $vars = [$vars]; - } - } - - return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars); - } - - return false; - } - /** * 字符串命名风格转换 - * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 * @access public - * @param string $name 字符串 - * @param integer $type 转换类型 + * @param string $name 字符串 + * @param integer $type 转换类型 * @param bool $ucfirst 首字母是否大写(驼峰规则) * @return string */ @@ -614,7 +375,6 @@ class Loader $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { return strtoupper($match[1]); }, $name); - return $ucfirst ? ucfirst($name) : lcfirst($name); } @@ -622,43 +382,28 @@ class Loader } /** - * 解析应用类的类名 + * 创建工厂对象实例 * @access public - * @param string $module 模块名 - * @param string $layer 层名 controller model ... - * @param string $name 类名 - * @param bool $appendSuffix 是否添加类名后缀 - * @return string + * @param string $name 工厂类名 + * @param string $namespace 默认命名空间 + * @return mixed */ - public static function parseClass($module, $layer, $name, $appendSuffix = false) + public static function factory($name, $namespace = '', ...$args) { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); - $array = explode('\\', str_replace(['/', '.'], '\\', $name)); - $class = self::parseName(array_pop($array), 1); - $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); - $path = $array ? implode('\\', $array) . '\\' : ''; - - return App::$namespace . '\\' . - ($module ? $module . '\\' : '') . - $layer . '\\' . $path . $class; - } - - /** - * 初始化类的实例 - * @access public - * @return void - */ - public static function clearInstance() - { - self::$instance = []; + if (class_exists($class)) { + return Container::getInstance()->invokeClass($class, $args); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } } } -// 作用范围隔离 - /** - * include - * @param string $file 文件路径 + * 作用范围隔离 + * + * @param $file * @return mixed */ function __include_file($file) @@ -666,11 +411,6 @@ function __include_file($file) return include $file; } -/** - * require - * @param string $file 文件路径 - * @return mixed - */ function __require_file($file) { return require $file; diff --git a/Server/thinkphp/library/think/Log.php b/Server/thinkphp/library/think/Log.php index c064306c..8902e976 100644 --- a/Server/thinkphp/library/think/Log.php +++ b/Server/thinkphp/library/think/Log.php @@ -11,134 +11,184 @@ namespace think; -use think\exception\ClassNotFoundException; - -/** - * Class Log - * @package think - * - * @method void log($msg) static 记录一般日志 - * @method void error($msg) static 记录错误日志 - * @method void info($msg) static 记录一般信息日志 - * @method void sql($msg) static 记录 SQL 查询日志 - * @method void notice($msg) static 记录提示日志 - * @method void alert($msg) static 记录报警日志 - */ -class Log +class Log implements LoggerInterface { - const LOG = 'log'; - const ERROR = 'error'; - const INFO = 'info'; - const SQL = 'sql'; - const NOTICE = 'notice'; - const ALERT = 'alert'; - const DEBUG = 'debug'; + const EMERGENCY = 'emergency'; + const ALERT = 'alert'; + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const NOTICE = 'notice'; + const INFO = 'info'; + const DEBUG = 'debug'; + const SQL = 'sql'; /** - * @var array 日志信息 + * 日志信息 + * @var array */ - protected static $log = []; + protected $log = []; /** - * @var array 配置参数 + * 配置参数 + * @var array */ - protected static $config = []; + protected $config = []; /** - * @var array 日志类型 + * 日志写入驱动 + * @var object */ - protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; + protected $driver; /** - * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动 + * 日志授权key + * @var string */ - protected static $driver; + protected $key; /** - * @var string 当前日志授权 key + * 是否允许日志写入 + * @var bool */ - protected static $key; + protected $allowWrite = true; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + public static function __make(App $app, Config $config) + { + return (new static($app))->init($config->pull('log')); + } /** * 日志初始化 * @access public - * @param array $config 配置参数 - * @return void + * @param array $config + * @return $this */ - public static function init($config = []) + public function init($config = []) { - $type = isset($config['type']) ? $config['type'] : 'File'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type); + $type = isset($config['type']) ? $config['type'] : 'File'; + + $this->config = $config; - self::$config = $config; unset($config['type']); - if (class_exists($class)) { - self::$driver = new $class($config); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); + if (!empty($config['close'])) { + $this->allowWrite = false; } - // 记录初始化信息 - App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); + $this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config); + + return $this; } /** * 获取日志信息 * @access public * @param string $type 信息类型 - * @return array|string + * @return array */ - public static function getLog($type = '') + public function getLog($type = '') { - return $type ? self::$log[$type] : self::$log; + return $type ? $this->log[$type] : $this->log; } /** - * 记录调试信息 + * 记录日志信息 * @access public - * @param mixed $msg 调试信息 - * @param string $type 信息类型 - * @return void + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this */ - public static function record($msg, $type = 'log') + public function record($msg, $type = 'info', array $context = []) { - self::$log[$type][] = $msg; + if (!$this->allowWrite) { + return; + } - // 命令行下面日志写入改进 - IS_CLI && self::save(); + if (is_string($msg) && !empty($context)) { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + $msg = strtr($msg, $replace); + } + + if (PHP_SAPI == 'cli') { + if (empty($this->config['level']) || in_array($type, $this->config['level'])) { + // 命令行日志实时写入 + $this->write($msg, $type, true); + } + } else { + $this->log[$type][] = $msg; + } + + return $this; } /** * 清空日志信息 * @access public - * @return void + * @return $this */ - public static function clear() + public function clear() { - self::$log = []; + $this->log = []; + + return $this; } /** - * 设置当前日志记录的授权 key + * 当前日志记录的授权key * @access public - * @param string $key 授权 key - * @return void + * @param string $key 授权key + * @return $this */ - public static function key($key) + public function key($key) { - self::$key = $key; + $this->key = $key; + + return $this; } /** * 检查日志写入权限 * @access public - * @param array $config 当前日志配置参数 + * @param array $config 当前日志配置参数 * @return bool */ - public static function check($config) + public function check($config) { - return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']); + if ($this->key && !empty($config['allow_key']) && !in_array($this->key, $config['allow_key'])) { + return false; + } + + return true; + } + + /** + * 关闭本次请求日志写入 + * @access public + * @return $this + */ + public function close() + { + $this->allowWrite = false; + $this->log = []; + + return $this; } /** @@ -146,41 +196,36 @@ class Log * @access public * @return bool */ - public static function save() + public function save() { - // 没有需要保存的记录则直接返回 - if (empty(self::$log)) { + if (empty($this->log) || !$this->allowWrite) { return true; } - is_null(self::$driver) && self::init(Config::get('log')); - - // 检测日志写入权限 - if (!self::check(self::$config)) { + if (!$this->check($this->config)) { + // 检测日志写入权限 return false; } - if (empty(self::$config['level'])) { - // 获取全部日志 - $log = self::$log; - if (!App::$debug && isset($log['debug'])) { - unset($log['debug']); + $log = []; + + foreach ($this->log as $level => $info) { + if (!$this->app->isDebug() && 'debug' == $level) { + continue; } - } else { - // 记录允许级别 - $log = []; - foreach (self::$config['level'] as $level) { - if (isset(self::$log[$level])) { - $log[$level] = self::$log[$level]; - } + + if (empty($this->config['level']) || in_array($level, $this->config['level'])) { + $log[$level] = $info; + + $this->app['hook']->listen('log_level', [$level, $info]); } } - if ($result = self::$driver->save($log, true)) { - self::$log = []; - } + $result = $this->driver->save($log, true); - Hook::listen('log_write_done', $log); + if ($result) { + $this->log = []; + } return $result; } @@ -189,49 +234,156 @@ class Log * 实时写入日志信息 并支持行为 * @access public * @param mixed $msg 调试信息 - * @param string $type 信息类型 + * @param string $type 日志级别 * @param bool $force 是否强制写入 * @return bool */ - public static function write($msg, $type = 'log', $force = false) + public function write($msg, $type = 'info', $force = false) { - $log = self::$log; + // 封装日志信息 + if (empty($this->config['level'])) { + $force = true; + } - // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录 - if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) { + if (true === $force || in_array($type, $this->config['level'])) { + $log[$type][] = $msg; + } else { return false; } - // 封装日志信息 - $log[$type][] = $msg; - - // 监听 log_write - Hook::listen('log_write', $log); - - is_null(self::$driver) && self::init(Config::get('log')); + // 监听log_write + $this->app['hook']->listen('log_write', $log); // 写入日志 - if ($result = self::$driver->save($log, false)) { - self::$log = []; - } - - return $result; + return $this->driver->save($log, false); } /** - * 静态方法调用 + * 记录日志信息 * @access public - * @param string $method 调用方法 - * @param mixed $args 参数 + * @param string $level 日志级别 + * @param mixed $message 日志信息 + * @param array $context 替换内容 * @return void */ - public static function __callStatic($method, $args) + public function log($level, $message, array $context = []) { - if (in_array($method, self::$type)) { - array_push($args, $method); - - call_user_func_array('\\think\\Log::record', $args); - } + $this->record($message, $level, $context); } + /** + * 记录emergency信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function emergency($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录警报信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function alert($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录紧急情况 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function critical($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录错误信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function error($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录warning信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function warning($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录notice信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function notice($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录一般信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function info($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录调试信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function debug($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录sql信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function sql($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } } diff --git a/Server/thinkphp/library/think/Model.php b/Server/thinkphp/library/think/Model.php index 2dc27b48..50f2ca14 100644 --- a/Server/thinkphp/library/think/Model.php +++ b/Server/thinkphp/library/think/Model.php @@ -11,109 +11,127 @@ namespace think; -use BadMethodCallException; use InvalidArgumentException; use think\db\Query; -use think\exception\ValidateException; -use think\model\Collection as ModelCollection; -use think\model\Relation; -use think\model\relation\BelongsTo; -use think\model\relation\BelongsToMany; -use think\model\relation\HasMany; -use think\model\relation\HasManyThrough; -use think\model\relation\HasOne; -use think\model\relation\MorphMany; -use think\model\relation\MorphOne; -use think\model\relation\MorphTo; /** * Class Model * @package think * @mixin Query + * @method $this scope(string|array $scope) static 查询范围 + * @method $this where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method $this whereRaw(string $where, array $bind = [], string $logic = 'AND') static 表达式查询 + * @method $this whereExp(string $field, string $condition, array $bind = [], string $logic = 'AND') static 字段表达式查询 + * @method $this when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询 + * @method $this join(mixed $join, mixed $condition = null, string $type = 'INNER', array $bind = []) static JOIN查询 + * @method $this view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method $this with(mixed $with, callable $callback = null) static 关联预载入 + * @method $this count(string $field = '*') static Count统计查询 + * @method $this min(string $field, bool $force = true) static Min统计查询 + * @method $this max(string $field, bool $force = true) static Max统计查询 + * @method $this sum(string $field) static SUM统计查询 + * @method $this avg(string $field) static Avg统计查询 + * @method $this field(mixed $field, boolean $except = false, string $tableName = '', string $prefix = '', string $alias = '') static 指定查询字段 + * @method $this fieldRaw(string $field) static 指定查询字段 + * @method $this union(mixed $union, boolean $all = false) static UNION查询 + * @method $this limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method $this order(mixed $field, string $order = null) static 查询ORDER + * @method $this orderRaw(string $field, array $bind = []) static 查询ORDER + * @method $this cache(mixed $key = null, integer|\DateTime $expire = null, string $tag = null) static 设置查询缓存 + * @method mixed value(string $field, mixed $default = null) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method $this find(mixed $data = null) static 查询单个记录 + * @method $this findOrFail(mixed $data = null) 查询单个记录 + * @method Collection|$this[] select(mixed $data = null) static 查询多个记录 + * @method $this get(mixed $data = null, mixed $with = [], bool $cache = false, bool $failException = false) static 查询单个记录 支持关联预载入 + * @method $this getOrFail(mixed $data = null, mixed $with = [], bool $cache = false) static 查询单个记录 不存在则抛出异常 + * @method $this findOrEmpty(mixed $data = null) static 查询单个记录 不存在则返回空模型 + * @method Collection|$this[] all(mixed $data = null, mixed $with = [], bool $cache = false) static 查询多个记录 支持关联预载入 + * @method $this withAttr(array $name, \Closure $closure = null) static 动态定义获取器 + * @method $this withJoin(string|array $with, string $joinType = '') static + * @method $this withCount(string|array $relation, bool $subQuery = true) static 关联统计 + * @method $this withSum(string|array $relation, string $field, bool $subQuery = true) static 关联SUM统计 + * @method $this withMax(string|array $relation, string $field, bool $subQuery = true) static 关联MAX统计 + * @method $this withMin(string|array $relation, string $field, bool $subQuery = true) static 关联Min统计 + * @method $this withAvg(string|array $relation, string $field, bool $subQuery = true) static 关联Avg统计 + * @method Paginator|$this paginate(int|array $listRows = null, int|bool $simple = false, array $config = []) static 分页 */ abstract class Model implements \JsonSerializable, \ArrayAccess { - // 数据库查询对象池 - protected static $links = []; - // 数据库配置 - protected $connection = []; - // 父关联模型对象 - protected $parent; - // 数据库查询对象 - protected $query; - // 当前模型名称 - protected $name; - // 数据表名称 - protected $table; - // 当前类名称 - protected $class; - // 回调事件 - private static $event = []; - // 错误信息 - protected $error; - // 字段验证规则 - protected $validate; - // 数据表主键 复合主键使用数组定义 不设置则自动获取 - protected $pk; - // 数据表字段信息 留空则自动获取 - protected $field = []; - // 数据排除字段 - protected $except = []; - // 数据废弃字段 - protected $disuse = []; - // 只读字段 - protected $readonly = []; - // 显示属性 - protected $visible = []; - // 隐藏属性 - protected $hidden = []; - // 追加属性 - protected $append = []; - // 数据信息 - protected $data = []; - // 原始数据 - protected $origin = []; - // 关联模型 - protected $relation = []; + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; - // 保存自动完成列表 + /** + * 是否存在数据 + * @var bool + */ + private $exists = false; + + /** + * 是否Replace + * @var bool + */ + private $replace = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置信息 + * @var array|string + */ + protected $connection = []; + + /** + * 数据库查询对象类名 + * @var string + */ + protected $query; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 写入自动完成定义 + * @var array + */ protected $auto = []; - // 新增自动完成列表 + + /** + * 新增自动完成定义 + * @var array + */ protected $insert = []; - // 更新自动完成列表 + + /** + * 更新自动完成定义 + * @var array + */ protected $update = []; - // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 - protected $autoWriteTimestamp; - // 创建时间字段 - protected $createTime = 'create_time'; - // 更新时间字段 - protected $updateTime = 'update_time'; - // 时间字段取出后的默认时间格式 - protected $dateFormat; - // 字段类型或者格式转换 - protected $type = []; - // 是否为更新数据 - protected $isUpdate = false; - // 是否使用Replace - protected $replace = false; - // 是否强制更新所有数据 - protected $force = false; - // 更新条件 - protected $updateWhere; - // 验证失败是否抛出异常 - protected $failException = false; - // 全局查询范围 - protected $useGlobalScope = true; - // 是否采用批量验证 - protected $batchValidate = false; - // 查询数据集对象 - protected $resultSetType; - // 关联自动写入 - protected $relationWrite; /** * 初始化过的模型. - * * @var array */ protected static $initialized = []; @@ -125,9 +143,33 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected static $readMaster; /** - * 构造方法 + * 查询对象实例 + * @var Query + */ + protected $queryInstance; + + /** + * 错误信息 + * @var mixed + */ + protected $error; + + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + + /** + * 全局查询范围 + * @var array + */ + protected $globalScope = []; + + /** + * 架构函数 * @access public - * @param array|object $data 数据 + * @param array|object $data 数据 */ public function __construct($data = []) { @@ -149,14 +191,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 记录原始数据 $this->origin = $this->data; - // 当前类名 - $this->class = get_called_class(); + $config = Db::getConfig(); if (empty($this->name)) { // 当前模型名 - $name = str_replace('\\', '/', $this->class); + $name = str_replace('\\', '/', static::class); $this->name = basename($name); - if (Config::get('class_suffix')) { + if (Container::get('config')->get('class_suffix')) { $suffix = basename(dirname($name)); $this->name = substr($this->name, 0, -strlen($suffix)); } @@ -164,76 +205,57 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 - $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); + $this->autoWriteTimestamp = $config['auto_timestamp']; } if (is_null($this->dateFormat)) { // 设置时间戳格式 - $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); + $this->dateFormat = $config['datetime_format']; } if (is_null($this->resultSetType)) { - $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); + $this->resultSetType = $config['resultset_type']; } + + if (!empty($this->connection) && is_array($this->connection)) { + // 设置模型的数据库连接 + $this->connection = array_merge($config, $this->connection); + } + + if ($this->observerClass) { + // 注册模型观察者 + static::observe($this->observerClass); + } + // 执行初始化操作 $this->initialize(); } + /** + * 获取当前模型名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + /** * 是否从主库读取数据(主从分布有效) * @access public - * @param bool $all 是否所有模型生效 + * @param bool $all 是否所有模型有效 * @return $this */ public function readMaster($all = false) { - $model = $all ? '*' : $this->class; + $model = $all ? '*' : static::class; static::$readMaster[$model] = true; + return $this; } - /** - * 创建模型的查询对象 - * @access protected - * @return Query - */ - protected function buildQuery() - { - // 合并数据库配置 - if (!empty($this->connection)) { - if (is_array($this->connection)) { - $connection = array_merge(Config::get('database'), $this->connection); - } else { - $connection = $this->connection; - } - } else { - $connection = []; - } - - $con = Db::connect($connection); - // 设置当前模型 确保查询返回模型对象 - $queryClass = $this->query ?: $con->getConfig('query'); - $query = new $queryClass($con, $this); - - if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) { - $query->master(true); - } - - // 设置当前数据表和模型名 - if (!empty($this->table)) { - $query->setTable($this->table); - } else { - $query->name($this->name); - } - - if (!empty($this->pk)) { - $query->pk($this->pk); - } - - return $query; - } - /** * 创建新的模型实例 * @access public @@ -248,39 +270,77 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 获取当前模型的查询对象 - * @access public - * @param bool $buildNewQuery 创建新的查询对象 + * 创建模型的查询对象 + * @access protected * @return Query */ - public function getQuery($buildNewQuery = false) + protected function buildQuery() { - if ($buildNewQuery) { - return $this->buildQuery(); - } elseif (!isset(self::$links[$this->class])) { - // 创建模型查询对象 - self::$links[$this->class] = $this->buildQuery(); + // 设置当前模型 确保查询返回模型对象 + $query = Db::connect($this->connection, false, $this->query); + $query->model($this) + ->name($this->name) + ->json($this->json, $this->jsonAssoc) + ->setJsonFieldType($this->jsonType); + + if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) { + $query->master(true); } - return self::$links[$this->class]; + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->table($this->table); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; } /** * 获取当前模型的数据库查询对象 * @access public - * @param bool $useBaseQuery 是否调用全局查询范围 - * @param bool $buildNewQuery 创建新的查询对象 + * @param Query $query 查询对象实例 + * @return $this + */ + public function setQuery($query) + { + $this->queryInstance = $query; + return $this; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool|array $useBaseQuery 是否调用全局查询范围(或者指定查询范围名称) * @return Query */ - public function db($useBaseQuery = true, $buildNewQuery = true) + public function db($useBaseQuery = true) { - $query = $this->getQuery($buildNewQuery); + if ($this->queryInstance) { + return $this->queryInstance; + } + + $query = $this->buildQuery(); + + // 软删除 + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { + $this->withNoTrashed($query); + } // 全局作用域 - if ($useBaseQuery && method_exists($this, 'base')) { + if (true === $useBaseQuery && method_exists($this, 'base')) { call_user_func_array([$this, 'base'], [ & $query]); } + $globalScope = is_array($useBaseQuery) && $useBaseQuery ? $useBaseQuery : $this->globalScope; + + if ($globalScope && false !== $useBaseQuery) { + $query->scope($globalScope); + } + // 返回当前模型的数据库查询对象 return $query; } @@ -292,9 +352,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected function initialize() { - $class = get_class($this); - if (!isset(static::$initialized[$class])) { - static::$initialized[$class] = true; + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; static::init(); } } @@ -305,1158 +364,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @return void */ protected static function init() - { - } - - /** - * 设置父关联对象 - * @access public - * @param Model $model 模型对象 - * @return $this - */ - public function setParent($model) - { - $this->parent = $model; - return $this; - } - - /** - * 获取父关联对象 - * @access public - * @return Model - */ - public function getParent() - { - return $this->parent; - } - - /** - * 设置数据对象值 - * @access public - * @param mixed $data 数据或者属性名 - * @param mixed $value 值 - * @return $this - */ - public function data($data, $value = null) - { - if (is_string($data)) { - $this->data[$data] = $value; - } else { - // 清空数据 - $this->data = []; - if (is_object($data)) { - $data = get_object_vars($data); - } - if (true === $value) { - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - } else { - $this->data = $data; - } - } - return $this; - } - - /** - * 获取对象原始数据 如果不存在指定字段返回false - * @access public - * @param string $name 字段名 留空获取全部 - * @return mixed - * @throws InvalidArgumentException - */ - public function getData($name = null) - { - if (is_null($name)) { - return $this->data; - } elseif (array_key_exists($name, $this->data)) { - return $this->data[$name]; - } elseif (array_key_exists($name, $this->relation)) { - return $this->relation[$name]; - } else { - throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); - } - } - - /** - * 是否需要自动写入时间字段 - * @access public - * @param bool $auto - * @return $this - */ - public function isAutoWriteTimestamp($auto) - { - $this->autoWriteTimestamp = $auto; - return $this; - } - - /** - * 更新是否强制写入数据 而不做比较 - * @access public - * @param bool $force - * @return $this - */ - public function force($force = true) - { - $this->force = $force; - return $this; - } - - /** - * 修改器 设置数据对象值 - * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @param array $data 数据 - * @return $this - */ - public function setAttr($name, $value, $data = []) - { - if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { - // 自动写入的时间戳字段 - $value = $this->autoWriteTimestamp($name); - } else { - // 检测修改器 - $method = 'set' . Loader::parseName($name, 1) . 'Attr'; - if (method_exists($this, $method)) { - $value = $this->$method($value, array_merge($this->data, $data), $this->relation); - } elseif (isset($this->type[$name])) { - // 类型转换 - $value = $this->writeTransform($value, $this->type[$name]); - } - } - - // 设置数据对象属性 - $this->data[$name] = $value; - return $this; - } - - /** - * 获取当前模型的关联模型数据 - * @access public - * @param string $name 关联方法名 - * @return mixed - */ - public function getRelation($name = null) - { - if (is_null($name)) { - return $this->relation; - } elseif (array_key_exists($name, $this->relation)) { - return $this->relation[$name]; - } else { - return; - } - } - - /** - * 设置关联数据对象值 - * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @return $this - */ - public function setRelation($name, $value) - { - $this->relation[$name] = $value; - return $this; - } - - /** - * 自动写入时间戳 - * @access public - * @param string $name 时间戳字段 - * @return mixed - */ - protected function autoWriteTimestamp($name) - { - if (isset($this->type[$name])) { - $type = $this->type[$name]; - if (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'datetime': - case 'date': - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime(time(), $format); - break; - case 'timestamp': - case 'integer': - default: - $value = time(); - break; - } - } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ - 'datetime', - 'date', - 'timestamp', - ]) - ) { - $value = $this->formatDateTime(time(), $this->dateFormat); - } else { - $value = $this->formatDateTime(time(), $this->dateFormat, true); - } - return $value; - } - - /** - * 时间日期字段格式化处理 - * @access public - * @param mixed $time 时间日期表达式 - * @param mixed $format 日期格式 - * @param bool $timestamp 是否进行时间戳转换 - * @return mixed - */ - protected function formatDateTime($time, $format, $timestamp = false) - { - if (false !== strpos($format, '\\')) { - $time = new $format($time); - } elseif (!$timestamp && false !== $format) { - $time = date($format, $time); - } - return $time; - } - - /** - * 数据写入 类型转换 - * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 - * @return mixed - */ - protected function writeTransform($value, $type) - { - if (is_null($value)) { - return; - } - - if (is_array($type)) { - list($type, $param) = $type; - } elseif (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'integer': - $value = (int) $value; - break; - case 'float': - if (empty($param)) { - $value = (float) $value; - } else { - $value = (float) number_format($value, $param, '.', ''); - } - break; - case 'boolean': - $value = (bool) $value; - break; - case 'timestamp': - if (!is_numeric($value)) { - $value = strtotime($value); - } - break; - case 'datetime': - $format = !empty($param) ? $param : $this->dateFormat; - $value = is_numeric($value) ? $value : strtotime($value); - $value = $this->formatDateTime($value, $format); - break; - case 'object': - if (is_object($value)) { - $value = json_encode($value, JSON_FORCE_OBJECT); - } - break; - case 'array': - $value = (array) $value; - case 'json': - $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; - $value = json_encode($value, $option); - break; - case 'serialize': - $value = serialize($value); - break; - - } - return $value; - } - - /** - * 获取器 获取数据对象的值 - * @access public - * @param string $name 名称 - * @return mixed - * @throws InvalidArgumentException - */ - public function getAttr($name) - { - try { - $notFound = false; - $value = $this->getData($name); - } catch (InvalidArgumentException $e) { - $notFound = true; - $value = null; - } - - // 检测属性获取器 - $method = 'get' . Loader::parseName($name, 1) . 'Attr'; - if (method_exists($this, $method)) { - $value = $this->$method($value, $this->data, $this->relation); - } elseif (isset($this->type[$name])) { - // 类型转换 - $value = $this->readTransform($value, $this->type[$name]); - } elseif (in_array($name, [$this->createTime, $this->updateTime])) { - if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ - 'datetime', - 'date', - 'timestamp', - ]) - ) { - $value = $this->formatDateTime(strtotime($value), $this->dateFormat); - } else { - $value = $this->formatDateTime($value, $this->dateFormat); - } - } elseif ($notFound) { - $relation = Loader::parseName($name, 1, false); - if (method_exists($this, $relation)) { - $modelRelation = $this->$relation(); - // 不存在该字段 获取关联数据 - $value = $this->getRelationData($modelRelation); - // 保存关联对象值 - $this->relation[$name] = $value; - } else { - throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); - } - } - return $value; - } - - /** - * 获取关联模型数据 - * @access public - * @param Relation $modelRelation 模型关联对象 - * @return mixed - * @throws BadMethodCallException - */ - protected function getRelationData(Relation $modelRelation) - { - if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { - $value = $this->parent; - } else { - // 首先获取关联数据 - if (method_exists($modelRelation, 'getRelation')) { - $value = $modelRelation->getRelation(); - } else { - throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation'); - } - } - return $value; - } - - /** - * 数据读取 类型转换 - * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 - * @return mixed - */ - protected function readTransform($value, $type) - { - if (is_null($value)) { - return; - } - - if (is_array($type)) { - list($type, $param) = $type; - } elseif (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'integer': - $value = (int) $value; - break; - case 'float': - if (empty($param)) { - $value = (float) $value; - } else { - $value = (float) number_format($value, $param, '.', ''); - } - break; - case 'boolean': - $value = (bool) $value; - break; - case 'timestamp': - if (!is_null($value)) { - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime($value, $format); - } - break; - case 'datetime': - if (!is_null($value)) { - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime(strtotime($value), $format); - } - break; - case 'json': - $value = json_decode($value, true); - break; - case 'array': - $value = empty($value) ? [] : json_decode($value, true); - break; - case 'object': - $value = empty($value) ? new \stdClass() : json_decode($value); - break; - case 'serialize': - try { - $value = unserialize($value); - } catch (\Exception $e) { - $value = null; - } - break; - default: - if (false !== strpos($type, '\\')) { - // 对象类型 - $value = new $type($value); - } - } - return $value; - } - - /** - * 设置需要追加的输出属性 - * @access public - * @param array $append 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function append($append = [], $override = false) - { - $this->append = $override ? $append : array_merge($this->append, $append); - return $this; - } - - /** - * 设置附加关联对象的属性 - * @access public - * @param string $relation 关联方法 - * @param string|array $append 追加属性名 - * @return $this - * @throws Exception - */ - public function appendRelationAttr($relation, $append) - { - if (is_string($append)) { - $append = explode(',', $append); - } - - $relation = Loader::parseName($relation, 1, false); - - // 获取关联数据 - if (isset($this->relation[$relation])) { - $model = $this->relation[$relation]; - } else { - $model = $this->getRelationData($this->$relation()); - } - - if ($model instanceof Model) { - foreach ($append as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($this->data[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $this->data[$key] = $model->getAttr($attr); - } - } - } - return $this; - } - - /** - * 设置需要隐藏的输出属性 - * @access public - * @param array $hidden 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function hidden($hidden = [], $override = false) - { - $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); - return $this; - } - - /** - * 设置需要输出的属性 - * @access public - * @param array $visible - * @param bool $override 是否覆盖 - * @return $this - */ - public function visible($visible = [], $override = false) - { - $this->visible = $override ? $visible : array_merge($this->visible, $visible); - return $this; - } - - /** - * 解析隐藏及显示属性 - * @access protected - * @param array $attrs 属性 - * @param array $result 结果集 - * @param bool $visible - * @return array - */ - protected function parseAttr($attrs, &$result, $visible = true) - { - $array = []; - foreach ($attrs as $key => $val) { - if (is_array($val)) { - if ($visible) { - $array[] = $key; - } - $result[$key] = $val; - } elseif (strpos($val, '.')) { - list($key, $name) = explode('.', $val); - if ($visible) { - $array[] = $key; - } - $result[$key][] = $name; - } else { - $array[] = $val; - } - } - return $array; - } - - /** - * 转换子模型对象 - * @access protected - * @param Model|ModelCollection $model - * @param $visible - * @param $hidden - * @param $key - * @return array - */ - protected function subToArray($model, $visible, $hidden, $key) - { - // 关联模型对象 - if (isset($visible[$key])) { - $model->visible($visible[$key]); - } elseif (isset($hidden[$key])) { - $model->hidden($hidden[$key]); - } - return $model->toArray(); - } - - /** - * 转换当前模型对象为数组 - * @access public - * @return array - */ - public function toArray() - { - $item = []; - $visible = []; - $hidden = []; - - $data = array_merge($this->data, $this->relation); - - // 过滤属性 - if (!empty($this->visible)) { - $array = $this->parseAttr($this->visible, $visible); - $data = array_intersect_key($data, array_flip($array)); - } elseif (!empty($this->hidden)) { - $array = $this->parseAttr($this->hidden, $hidden, false); - $data = array_diff_key($data, array_flip($array)); - } - - foreach ($data as $key => $val) { - if ($val instanceof Model || $val instanceof ModelCollection) { - // 关联模型对象 - $item[$key] = $this->subToArray($val, $visible, $hidden, $key); - } elseif (is_array($val) && reset($val) instanceof Model) { - // 关联模型数据集 - $arr = []; - foreach ($val as $k => $value) { - $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); - } - $item[$key] = $arr; - } else { - // 模型属性 - $item[$key] = $this->getAttr($key); - } - } - // 追加属性(必须定义获取器) - if (!empty($this->append)) { - foreach ($this->append as $key => $name) { - if (is_array($name)) { - // 追加关联对象属性 - $relation = $this->getAttr($key); - $item[$key] = $relation->append($name)->toArray(); - } elseif (strpos($name, '.')) { - list($key, $attr) = explode('.', $name); - // 追加关联对象属性 - $relation = $this->getAttr($key); - $item[$key] = $relation->append([$attr])->toArray(); - } else { - $relation = Loader::parseName($name, 1, false); - if (method_exists($this, $relation)) { - $modelRelation = $this->$relation(); - $value = $this->getRelationData($modelRelation); - - if (method_exists($modelRelation, 'getBindAttr')) { - $bindAttr = $modelRelation->getBindAttr(); - if ($bindAttr) { - foreach ($bindAttr as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($this->data[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $item[$key] = $value ? $value->getAttr($attr) : null; - } - } - continue; - } - } - $item[$name] = $value; - } else { - $item[$name] = $this->getAttr($name); - } - } - } - } - return !empty($item) ? $item : []; - } - - /** - * 转换当前模型对象为JSON字符串 - * @access public - * @param integer $options json参数 - * @return string - */ - public function toJson($options = JSON_UNESCAPED_UNICODE) - { - return json_encode($this->toArray(), $options); - } - - /** - * 移除当前模型的关联属性 - * @access public - * @return $this - */ - public function removeRelation() - { - $this->relation = []; - return $this; - } - - /** - * 转换当前模型数据集为数据集对象 - * @access public - * @param array|\think\Collection $collection 数据集 - * @return \think\Collection - */ - public function toCollection($collection) - { - if ($this->resultSetType) { - if ('collection' == $this->resultSetType) { - $collection = new ModelCollection($collection); - } elseif (false !== strpos($this->resultSetType, '\\')) { - $class = $this->resultSetType; - $collection = new $class($collection); - } - } - return $collection; - } - - /** - * 关联数据一起更新 - * @access public - * @param mixed $relation 关联 - * @return $this - */ - public function together($relation) - { - if (is_string($relation)) { - $relation = explode(',', $relation); - } - $this->relationWrite = $relation; - return $this; - } - - /** - * 获取模型对象的主键 - * @access public - * @param string $name 模型名 - * @return mixed - */ - public function getPk($name = '') - { - if (!empty($name)) { - $table = $this->getQuery()->getTable($name); - return $this->getQuery()->getPk($table); - } elseif (empty($this->pk)) { - $this->pk = $this->getQuery()->getPk(); - } - return $this->pk; - } - - /** - * 判断一个字段名是否为主键字段 - * @access public - * @param string $key 名称 - * @return bool - */ - protected function isPk($key) - { - $pk = $this->getPk(); - if (is_string($pk) && $pk == $key) { - return true; - } elseif (is_array($pk) && in_array($key, $pk)) { - return true; - } - return false; - } - - /** - * 新增数据是否使用Replace - * @access public - * @param bool $replace - * @return $this - */ - public function replace($replace = true) - { - $this->replace = $replace; - return $this; - } - - /** - * 保存当前数据对象 - * @access public - * @param array $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return integer|false - */ - public function save($data = [], $where = [], $sequence = null) - { - if (is_string($data)) { - $sequence = $data; - $data = []; - } - - // 数据自动验证 - if (!empty($data)) { - if (!$this->validateData($data)) { - return false; - } - - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - } - - if (!empty($where)) { - $this->isUpdate = true; - $this->updateWhere = $where; - } - - // 自动关联写入 - if (!empty($this->relationWrite)) { - $relation = []; - foreach ($this->relationWrite as $key => $name) { - if (is_array($name)) { - if (key($name) === 0) { - $relation[$key] = []; - foreach ($name as $val) { - if (isset($this->data[$val])) { - $relation[$key][$val] = $this->data[$val]; - unset($this->data[$val]); - } - } - } else { - $relation[$key] = $name; - } - } elseif (isset($this->relation[$name])) { - $relation[$name] = $this->relation[$name]; - } elseif (isset($this->data[$name])) { - $relation[$name] = $this->data[$name]; - unset($this->data[$name]); - } - } - } - - // 数据自动完成 - $this->autoCompleteData($this->auto); - - // 事件回调 - if (false === $this->trigger('before_write', $this)) { - return false; - } - $pk = $this->getPk(); - if ($this->isUpdate) { - // 自动更新 - $this->autoCompleteData($this->update); - - // 事件回调 - if (false === $this->trigger('before_update', $this)) { - return false; - } - - // 获取有更新的数据 - $data = $this->getChangedData(); - - if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { - // 关联更新 - if (isset($relation)) { - $this->autoRelationUpdate($relation); - } - return 0; - } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { - // 自动写入更新时间 - $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); - $this->data[$this->updateTime] = $data[$this->updateTime]; - } - - if (empty($where) && !empty($this->updateWhere)) { - $where = $this->updateWhere; - } - - // 保留主键数据 - foreach ($this->data as $key => $val) { - if ($this->isPk($key)) { - $data[$key] = $val; - } - } - - $array = []; - - foreach ((array) $pk as $key) { - if (isset($data[$key])) { - $array[$key] = $data[$key]; - unset($data[$key]); - } - } - - if (!empty($array)) { - $where = $array; - } - - // 检测字段 - $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update)); - - // 模型更新 - if (!empty($allowFields)) { - $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data); - } else { - $result = $this->getQuery()->where($where)->update($data); - } - - // 关联更新 - if (isset($relation)) { - $this->autoRelationUpdate($relation); - } - - // 更新回调 - $this->trigger('after_update', $this); - - } else { - // 自动写入 - $this->autoCompleteData($this->insert); - - // 自动写入创建时间和更新时间 - if ($this->autoWriteTimestamp) { - if ($this->createTime && !isset($this->data[$this->createTime])) { - $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); - } - if ($this->updateTime && !isset($this->data[$this->updateTime])) { - $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); - } - } - - if (false === $this->trigger('before_insert', $this)) { - return false; - } - - // 检测字段 - $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); - if (!empty($allowFields)) { - $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence); - } else { - $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence); - } - - // 获取自动增长主键 - if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) { - foreach ((array) $pk as $key) { - if (!isset($this->data[$key]) || '' == $this->data[$key]) { - $this->data[$key] = $insertId; - } - } - } - - // 关联写入 - if (isset($relation)) { - foreach ($relation as $name => $val) { - $method = Loader::parseName($name, 1, false); - $this->$method()->save($val); - } - } - - // 标记为更新 - $this->isUpdate = true; - - // 新增回调 - $this->trigger('after_insert', $this); - } - // 写入回调 - $this->trigger('after_write', $this); - - // 重新记录原始数据 - $this->origin = $this->data; - - return $result; - } - - protected function checkAllowField($auto = []) - { - if (true === $this->field) { - $this->field = $this->getQuery()->getTableInfo('', 'fields'); - $field = $this->field; - } elseif (!empty($this->field)) { - $field = array_merge($this->field, $auto); - if ($this->autoWriteTimestamp) { - array_push($field, $this->createTime, $this->updateTime); - } - } elseif (!empty($this->except)) { - $fields = $this->getQuery()->getTableInfo('', 'fields'); - $field = array_diff($fields, (array) $this->except); - $this->field = $field; - } else { - $field = []; - } - - if ($this->disuse) { - // 废弃字段 - $field = array_diff($field, (array) $this->disuse); - } - return $field; - } - - protected function autoRelationUpdate($relation) - { - foreach ($relation as $name => $val) { - if ($val instanceof Model) { - $val->save(); - } else { - unset($this->data[$name]); - $model = $this->getAttr($name); - if ($model instanceof Model) { - $model->save($val); - } - } - } - } - - /** - * 获取变化的数据 并排除只读数据 - * @access public - * @return array - */ - public function getChangedData() - { - if ($this->force) { - $data = $this->data; - } else { - $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { - if ((empty($a) || empty($b)) && $a !== $b) { - return 1; - } - return is_object($a) || $a != $b ? 1 : 0; - }); - } - - if (!empty($this->readonly)) { - // 只读字段不允许更新 - foreach ($this->readonly as $key => $field) { - if (isset($data[$field])) { - unset($data[$field]); - } - } - } - - return $data; - } - - /** - * 字段值(延迟)增长 - * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setInc($field, $step = 1, $lazyTime = 0) - { - // 更新条件 - $where = $this->getWhere(); - - $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime); - if (true !== $result) { - $this->data[$field] += $step; - } - - return $result; - } - - /** - * 字段值(延迟)增长 - * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setDec($field, $step = 1, $lazyTime = 0) - { - // 更新条件 - $where = $this->getWhere(); - $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime); - if (true !== $result) { - $this->data[$field] -= $step; - } - - return $result; - } - - /** - * 获取更新条件 - * @access protected - * @return mixed - */ - protected function getWhere() - { - // 删除条件 - $pk = $this->getPk(); - - if (is_string($pk) && isset($this->data[$pk])) { - $where = [$pk => $this->data[$pk]]; - } elseif (!empty($this->updateWhere)) { - $where = $this->updateWhere; - } else { - $where = null; - } - return $where; - } - - /** - * 保存多个数据到当前数据对象 - * @access public - * @param array $dataSet 数据 - * @param boolean $replace 是否自动识别更新和写入 - * @return array|false - * @throws \Exception - */ - public function saveAll($dataSet, $replace = true) - { - if ($this->validate) { - // 数据批量验证 - $validate = $this->validate; - foreach ($dataSet as $data) { - if (!$this->validateData($data, $validate)) { - return false; - } - } - } - - $result = []; - $db = $this->getQuery(); - $db->startTrans(); - try { - $pk = $this->getPk(); - if (is_string($pk) && $replace) { - $auto = true; - } - foreach ($dataSet as $key => $data) { - if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { - $result[$key] = self::update($data, [], $this->field); - } else { - $result[$key] = self::create($data, $this->field); - } - } - $db->commit(); - return $this->toCollection($result); - } catch (\Exception $e) { - $db->rollback(); - throw $e; - } - } - - /** - * 设置允许写入的字段 - * @access public - * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段 - * @return $this - */ - public function allowField($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->field = $field; - return $this; - } - - /** - * 设置排除写入的字段 - * @access public - * @param string|array $field 排除允许写入的字段 - * @return $this - */ - public function except($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->except = $field; - return $this; - } - - /** - * 设置只读字段 - * @access public - * @param mixed $field 只读字段 - * @return $this - */ - public function readonly($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->readonly = $field; - return $this; - } - - /** - * 是否为更新数据 - * @access public - * @param bool $update - * @param mixed $where - * @return $this - */ - public function isUpdate($update = true, $where = null) - { - $this->isUpdate = $update; - if (!empty($where)) { - $this->updateWhere = $where; - } - return $this; - } + {} /** * 数据自动完成 - * @access public - * @param array $auto 要自动更新的字段列表 + * @access protected + * @param array $auto 要自动更新的字段列表 * @return void */ protected function autoCompleteData($auto = []) @@ -1478,132 +391,617 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 删除当前的记录 + * 更新是否强制写入数据 而不做比较 * @access public - * @return integer + * @param bool $force + * @return $this */ - public function delete() + public function force($force = true) { - if (false === $this->trigger('before_delete', $this)) { + $this->force = $force; + return $this; + } + + /** + * 判断force + * @access public + * @return bool + */ + public function isForce() + { + return $this->force; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return $this + */ + public function exists($exists) + { + $this->exists = $exists; + return $this; + } + + /** + * 判断数据是否存在数据库 + * @access public + * @return bool + */ + public function isExists() + { + return $this->exists; + } + + /** + * 判断模型是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->data); + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + if (!$this->checkBeforeSave($data, $where)) { return false; } - // 删除条件 - $where = $this->getWhere(); + $result = $this->exists ? $this->updateData($where) : $this->insertData($sequence); - // 删除当前模型数据 - $result = $this->getQuery()->where($where)->delete(); + if (false === $result) { + return false; + } - // 关联删除 - if (!empty($this->relationWrite)) { - foreach ($this->relationWrite as $key => $name) { - $name = is_numeric($key) ? $name : $key; - $model = $this->getAttr($name); - if ($model instanceof Model) { - $model->delete(); + // 写入回调 + $this->trigger('after_write'); + + // 重新记录原始数据 + $this->origin = $this->data; + $this->set = []; + + return true; + } + + /** + * 写入之前检查数据 + * @access protected + * @param array $data 数据 + * @param array $where 保存条件 + * @return bool + */ + protected function checkBeforeSave($data, $where) + { + if (!empty($data)) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + + if (!empty($where)) { + $this->exists = true; + $this->updateWhere = $where; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write')) { + return false; + } + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @param array $append 自动完成的字段列表 + * @return array + */ + protected function checkAllowFields(array $append = []) + { + // 检测字段 + if (empty($this->field) || true === $this->field) { + $query = $this->db(false); + $table = $this->table ?: $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + + $field = $this->field; + } else { + $field = array_merge($this->field, $append); + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + + return $field; + } + + /** + * 更新写入数据 + * @access protected + * @param mixed $where 更新条件 + * @return bool + */ + protected function updateData($where) + { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return true; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->update)); + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $pk = $this->getPk(); + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[] = [$key, '=', $data[$key]]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + foreach ((array) $this->relationWrite as $name => $val) { + if (is_array($val)) { + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } } } } - $this->trigger('after_delete', $this); - // 清空原始数据 - $this->origin = []; + // 模型更新 + $db = $this->db(false); + $db->startTrans(); - return $result; + try { + $db->where($where) + ->strict(false) + ->field($allowFields) + ->update($data); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + $db->commit(); + + // 更新回调 + $this->trigger('after_update'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增序列名 + * @return bool + */ + protected function insertData($sequence) + { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 时间戳自动写入 + $this->checkTimeStampWrite(); + + if (false === $this->trigger('before_insert')) { + return false; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert)); + + $db = $this->db(false); + $db->startTrans(); + + try { + $result = $db->strict(false) + ->field($allowFields) + ->insert($this->data, $this->replace, false, $sequence); + + // 获取自动增长主键 + if ($result && $insertId = $db->getLastInsID($sequence)) { + $pk = $this->getPk(); + + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + + $db->commit(); + + // 标记为更新 + $this->exists = true; + + // 新增回调 + $this->trigger('after_insert'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return bool + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + $result = $this->db(false) + ->where($where) + ->setInc($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] += $step; + } + + // 更新回调 + $this->trigger('after_update'); + + return true; + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return bool + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + $result = $this->db(false) + ->where($where) + ->setDec($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] -= $step; + } + + // 更新回调 + $this->trigger('after_update'); + + return true; + } + + /** + * 获取当前的更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + $where = []; + if (is_string($pk) && isset($this->data[$pk])) { + $where[] = [$pk, '=', $this->data[$pk]]; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($this->data[$field])) { + $where[] = [$field, '=', $this->data[$field]]; + } + } + } + + if (empty($where)) { + $where = empty($this->updateWhere) ? null : $this->updateWhere; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + $db = $this->db(false); + $db->startTrans(); + + try { + $pk = $this->getPk(); + + if (is_string($pk) && $replace) { + $auto = true; + } + + $result = []; + + foreach ($dataSet as $key => $data) { + if ($this->exists || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field, $this->replace); + } + } + + $db->commit(); + + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 是否为更新数据 + * @access public + * @param mixed $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + if (is_bool($update)) { + $this->exists = $update; + + if (!empty($where)) { + $this->updateWhere = $where; + } + } else { + $this->exists = true; + $this->updateWhere = $update; + } + + return $this; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete() + { + if (!$this->exists || false === $this->trigger('before_delete')) { + return false; + } + + // 读取更新条件 + $where = $this->getWhere(); + + $db = $this->db(false); + $db->startTrans(); + + try { + // 删除当前模型数据 + $db->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $db->commit(); + + $this->trigger('after_delete'); + + $this->exists = false; + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } } /** * 设置自动完成的字段( 规则通过修改器定义) * @access public - * @param array $fields 需要自动完成的字段 + * @param array $fields 需要自动完成的字段 * @return $this */ public function auto($fields) { $this->auto = $fields; + return $this; } /** - * 设置字段验证 + * 写入数据 * @access public - * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 - * @param array $msg 提示信息 - * @param bool $batch 批量验证 - * @return $this + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @param bool $replace 使用Replace + * @return static */ - public function validate($rule = true, $msg = [], $batch = false) + public static function create($data = [], $field = null, $replace = false) { - if (is_array($rule)) { - $this->validate = [ - 'rule' => $rule, - 'msg' => $msg, - ]; - } else { - $this->validate = true === $rule ? $this->name : $rule; + $model = new static(); + + if (!empty($field)) { + $model->allowField($field); } - $this->batchValidate = $batch; - return $this; + + $model->isUpdate(false)->replace($replace)->save($data, []); + + return $model; } /** - * 设置验证失败后是否抛出异常 + * 更新数据 * @access public - * @param bool $fail 是否抛出异常 - * @return $this + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return static */ - public function validateFailException($fail = true) + public static function update($data = [], $where = [], $field = null) { - $this->failException = $fail; - return $this; + $model = new static(); + + if (!empty($field)) { + $model->allowField($field); + } + + $model->isUpdate(true)->save($data, $where); + + return $model; } /** - * 自动验证数据 - * @access protected - * @param array $data 验证数据 - * @param mixed $rule 验证规则 - * @param bool $batch 批量验证 + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 * @return bool */ - protected function validateData($data, $rule = null, $batch = null) + public static function destroy($data) { - $info = is_null($rule) ? $this->validate : $rule; - - if (!empty($info)) { - if (is_array($info)) { - $validate = Loader::validate(); - $validate->rule($info['rule']); - $validate->message($info['msg']); - } else { - $name = is_string($info) ? $info : $this->name; - if (strpos($name, '.')) { - list($name, $scene) = explode('.', $name); - } - $validate = Loader::validate($name); - if (!empty($scene)) { - $validate->scene($scene); - } - } - $batch = is_null($batch) ? $this->batchValidate : $batch; - - if (!$validate->batch($batch)->check($data)) { - $this->error = $validate->getError(); - if ($this->failException) { - throw new ValidateException($this->error); - } else { - return false; - } - } - $this->validate = null; + if (empty($data) && 0 !== $data) { + return false; } + + $model = new static(); + + $query = $model->db(); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($query); + $data = null; + } + + $resultSet = $query->select($data); + + if ($resultSet) { + foreach ($resultSet as $data) { + $data->delete(); + } + } + return true; } /** - * 返回模型的错误信息 + * 获取错误信息 * @access public - * @return string|array + * @return mixed */ public function getError() { @@ -1611,607 +1009,26 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 注册回调方法 - * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 - * @param bool $override 是否覆盖 - * @return void + * 解序列化后处理 */ - public static function event($event, $callback, $override = false) + public function __wakeup() { - $class = get_called_class(); - if ($override) { - self::$event[$class][$event] = []; - } - self::$event[$class][$event][] = $callback; + $this->initialize(); } - /** - * 触发事件 - * @access protected - * @param string $event 事件名 - * @param mixed $params 传入参数(引用) - * @return bool - */ - protected function trigger($event, &$params) + public function __debugInfo() { - if (isset(self::$event[$this->class][$event])) { - foreach (self::$event[$this->class][$event] as $callback) { - if (is_callable($callback)) { - $result = call_user_func_array($callback, [ & $params]); - if (false === $result) { - return false; - } - } - } - } - return true; - } - - /** - * 写入数据 - * @access public - * @param array $data 数据数组 - * @param array|true $field 允许字段 - * @return $this - */ - public static function create($data = [], $field = null) - { - $model = new static(); - if (!empty($field)) { - $model->allowField($field); - } - $model->isUpdate(false)->save($data, []); - return $model; - } - - /** - * 更新数据 - * @access public - * @param array $data 数据数组 - * @param array $where 更新条件 - * @param array|true $field 允许字段 - * @return $this - */ - public static function update($data = [], $where = [], $field = null) - { - $model = new static(); - if (!empty($field)) { - $model->allowField($field); - } - $result = $model->isUpdate(true)->save($data, $where); - return $model; - } - - /** - * 查找单条记录 - * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache 是否缓存 - * @return static|null - * @throws exception\DbException - */ - public static function get($data, $with = [], $cache = false) - { - if (is_null($data)) { - return; - } - - if (true === $with || is_int($with)) { - $cache = $with; - $with = []; - } - $query = static::parseQuery($data, $with, $cache); - return $query->find($data); - } - - /** - * 查找所有记录 - * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache 是否缓存 - * @return static[]|false - * @throws exception\DbException - */ - public static function all($data = null, $with = [], $cache = false) - { - if (true === $with || is_int($with)) { - $cache = $with; - $with = []; - } - $query = static::parseQuery($data, $with, $cache); - return $query->select($data); - } - - /** - * 分析查询表达式 - * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 - * @return Query - */ - protected static function parseQuery(&$data, $with, $cache) - { - $result = self::with($with)->cache($cache); - if (is_array($data) && key($data) !== 0) { - $result = $result->where($data); - $data = null; - } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $result]); - $data = null; - } elseif ($data instanceof Query) { - $result = $data->with($with)->cache($cache); - $data = null; - } - return $result; - } - - /** - * 删除记录 - * @access public - * @param mixed $data 主键列表 支持闭包查询条件 - * @return integer 成功删除的记录数 - */ - public static function destroy($data) - { - $model = new static(); - $query = $model->db(); - if (empty($data) && 0 !== $data) { - return 0; - } elseif (is_array($data) && key($data) !== 0) { - $query->where($data); - $data = null; - } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $query]); - $data = null; - } - $resultSet = $query->select($data); - $count = 0; - if ($resultSet) { - foreach ($resultSet as $data) { - $result = $data->delete(); - $count += $result; - } - } - return $count; - } - - /** - * 命名范围 - * @access public - * @param string|array|\Closure $name 命名范围名称 逗号分隔 - * @internal mixed ...$params 参数调用 - * @return Query - */ - public static function scope($name) - { - $model = new static(); - $query = $model->db(); - $params = func_get_args(); - array_shift($params); - array_unshift($params, $query); - if ($name instanceof \Closure) { - call_user_func_array($name, $params); - } elseif (is_string($name)) { - $name = explode(',', $name); - } - if (is_array($name)) { - foreach ($name as $scope) { - $method = 'scope' . trim($scope); - if (method_exists($model, $method)) { - call_user_func_array([$model, $method], $params); - } - } - } - return $query; - } - - /** - * 设置是否使用全局查询范围 - * @param bool $use 是否启用全局查询范围 - * @access public - * @return Query - */ - public static function useGlobalScope($use) - { - $model = new static(); - return $model->db($use); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $relation 关联方法名 - * @param mixed $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @return Relation|Query - */ - public static function has($relation, $operator = '>=', $count = 1, $id = '*') - { - $relation = (new static())->$relation(); - if (is_array($operator) || $operator instanceof \Closure) { - return $relation->hasWhere($operator); - } - return $relation->has($operator, $count, $id); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $relation 关联方法名 - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Relation|Query - */ - public static function hasWhere($relation, $where = [], $fields = null) - { - return (new static())->$relation()->hasWhere($where, $fields); - } - - /** - * 解析模型的完整命名空间 - * @access public - * @param string $model 模型名(或者完整类名) - * @return string - */ - protected function parseModel($model) - { - if (false === strpos($model, '\\')) { - $path = explode('\\', get_called_class()); - array_pop($path); - array_push($path, Loader::parseName($model, 1)); - $model = implode('\\', $path); - } - return $model; - } - - /** - * 查询当前模型的关联数据 - * @access public - * @param string|array $relations 关联名 - * @return $this - */ - public function relationQuery($relations) - { - if (is_string($relations)) { - $relations = explode(',', $relations); - } - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = null; - if ($relation instanceof \Closure) { - // 支持闭包查询过滤关联条件 - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $method = Loader::parseName($relation, 1, false); - $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); - } - return $this; - } - - /** - * 预载入关联查询 返回数据集 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 - * @return array - */ - public function eagerlyResultSet(&$resultSet, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); - } - } - - /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 - * @return Model - */ - public function eagerlyResult(&$result, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); - } - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param string|array $relation 关联名 - * @return void - */ - public function relationCount(&$result, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } elseif (is_string($key)) { - $name = $relation; - $relation = $key; - } - $relation = Loader::parseName($relation, 1, false); - $count = $this->$relation()->relationCount($result, $closure); - if (!isset($name)) { - $name = Loader::parseName($relation) . '_count'; - } - $result->setAttr($name, $count); - } - } - - /** - * 获取模型的默认外键名 - * @access public - * @param string $name 模型名 - * @return string - */ - protected function getForeignKey($name) - { - if (strpos($name, '\\')) { - $name = basename(str_replace('\\', '/', $name)); - } - return Loader::parseName($name) . '_id'; - } - - /** - * HAS ONE 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @param array $alias 别名定义(已经废弃) - * @param string $joinType JOIN类型 - * @return HasOne - */ - public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - return new HasOne($this, $model, $foreignKey, $localKey, $joinType); - } - - /** - * BELONGS TO 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义(已经废弃) - * @param string $joinType JOIN类型 - * @return BelongsTo - */ - public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: $this->getForeignKey($model); - $localKey = $localKey ?: (new $model)->getPk(); - $trace = debug_backtrace(false, 2); - $relation = Loader::parseName($trace[1]['function']); - return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); - } - - /** - * HAS MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @return HasMany - */ - public function hasMany($model, $foreignKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - return new HasMany($this, $model, $foreignKey, $localKey); - } - - /** - * HAS MANY 远程关联定义 - * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $foreignKey 关联外键 - * @param string $throughKey 关联外键 - * @param string $localKey 当前模型主键 - * @return HasManyThrough - */ - public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $through = $this->parseModel($through); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - $throughKey = $throughKey ?: $this->getForeignKey($through); - return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); - } - - /** - * BELONGS TO MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型关联键 - * @return BelongsToMany - */ - public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $name = Loader::parseName(basename(str_replace('\\', '/', $model))); - $table = $table ?: Loader::parseName($this->name) . '_' . $name; - $foreignKey = $foreignKey ?: $name . '_id'; - $localKey = $localKey ?: $this->getForeignKey($this->name); - return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); - } - - /** - * MORPH MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string|array $morph 多态字段信息 - * @param string $type 多态类型 - * @return MorphMany - */ - public function morphMany($model, $morph = null, $type = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - if (is_null($morph)) { - $trace = debug_backtrace(false, 2); - $morph = Loader::parseName($trace[1]['function']); - } - $type = $type ?: get_class($this); - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphMany($this, $model, $foreignKey, $morphType, $type); - } - - /** - * MORPH One 关联定义 - * @access public - * @param string $model 模型名 - * @param string|array $morph 多态字段信息 - * @param string $type 多态类型 - * @return MorphOne - */ - public function morphOne($model, $morph = null, $type = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - if (is_null($morph)) { - $trace = debug_backtrace(false, 2); - $morph = Loader::parseName($trace[1]['function']); - } - $type = $type ?: get_class($this); - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphOne($this, $model, $foreignKey, $morphType, $type); - } - - /** - * MORPH TO 关联定义 - * @access public - * @param string|array $morph 多态字段信息 - * @param array $alias 多态别名定义 - * @return MorphTo - */ - public function morphTo($morph = null, $alias = []) - { - $trace = debug_backtrace(false, 2); - $relation = Loader::parseName($trace[1]['function']); - - if (is_null($morph)) { - $morph = $relation; - } - // 记录当前关联信息 - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); - } - - public function __call($method, $args) - { - $query = $this->db(true, false); - if (method_exists($this, 'scope' . $method)) { - // 动态调用命名范围 - $method = 'scope' . $method; - array_unshift($args, $query); - call_user_func_array([$this, $method], $args); - return $this; - } else { - return call_user_func_array([$query, $method], $args); - } - } - - public static function __callStatic($method, $args) - { - $model = new static(); - $query = $model->db(); - if (method_exists($model, 'scope' . $method)) { - // 动态调用命名范围 - $method = 'scope' . $method; - array_unshift($args, $query); - - call_user_func_array([$model, $method], $args); - return $query; - } else { - return call_user_func_array([$query, $method], $args); - } + return [ + 'data' => $this->data, + 'relation' => $this->relation, + ]; } /** * 修改器 设置数据对象的值 * @access public - * @param string $name 名称 - * @param mixed $value 值 + * @param string $name 名称 + * @param mixed $value 值 * @return void */ public function __set($name, $value) @@ -2222,7 +1039,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 获取器 获取数据对象的值 * @access public - * @param string $name 名称 + * @param string $name 名称 * @return mixed */ public function __get($name) @@ -2233,28 +1050,22 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 检测数据对象的值 * @access public - * @param string $name 名称 + * @param string $name 名称 * @return boolean */ public function __isset($name) { try { - if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { - return true; - } else { - $this->getAttr($name); - return true; - } + return !is_null($this->getAttr($name)); } catch (InvalidArgumentException $e) { return false; } - } /** * 销毁数据对象的值 * @access public - * @param string $name 名称 + * @param string $name 名称 * @return void */ public function __unset($name) @@ -2262,17 +1073,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess unset($this->data[$name], $this->relation[$name]); } - public function __toString() - { - return $this->toJson(); - } - - // JsonSerializable - public function jsonSerialize() - { - return $this->toArray(); - } - // ArrayAccess public function offsetSet($name, $value) { @@ -2295,56 +1095,31 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 解序列化后处理 + * 设置是否使用全局查询范围 + * @access public + * @param bool|array $use 是否启用全局查询范围(或者用数组指定查询范围名称) + * @return Query */ - public function __wakeup() + public static function useGlobalScope($use) { - $this->initialize(); + $model = new static(); + + return $model->db($use); } - /** - * 模型事件快捷方法 - * @param $callback - * @param bool $override - */ - protected static function beforeInsert($callback, $override = false) + public function __call($method, $args) { - self::event('before_insert', $callback, $override); + if ('withattr' == strtolower($method)) { + return call_user_func_array([$this, 'withAttribute'], $args); + } + + return call_user_func_array([$this->db(), $method], $args); } - protected static function afterInsert($callback, $override = false) + public static function __callStatic($method, $args) { - self::event('after_insert', $callback, $override); - } + $model = new static(); - protected static function beforeUpdate($callback, $override = false) - { - self::event('before_update', $callback, $override); + return call_user_func_array([$model->db(), $method], $args); } - - protected static function afterUpdate($callback, $override = false) - { - self::event('after_update', $callback, $override); - } - - protected static function beforeWrite($callback, $override = false) - { - self::event('before_write', $callback, $override); - } - - protected static function afterWrite($callback, $override = false) - { - self::event('after_write', $callback, $override); - } - - protected static function beforeDelete($callback, $override = false) - { - self::event('before_delete', $callback, $override); - } - - protected static function afterDelete($callback, $override = false) - { - self::event('after_delete', $callback, $override); - } - } diff --git a/Server/thinkphp/library/think/Paginator.php b/Server/thinkphp/library/think/Paginator.php index 36555678..bbe63e2e 100644 --- a/Server/thinkphp/library/think/Paginator.php +++ b/Server/thinkphp/library/think/Paginator.php @@ -20,28 +20,52 @@ use Traversable; abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable { - /** @var bool 是否为简洁模式 */ + /** + * 是否简洁模式 + * @var bool + */ protected $simple = false; - /** @var Collection 数据集 */ + /** + * 数据集 + * @var Collection + */ protected $items; - /** @var integer 当前页 */ + /** + * 当前页 + * @var integer + */ protected $currentPage; - /** @var integer 最后一页 */ + /** + * 最后一页 + * @var integer + */ protected $lastPage; - /** @var integer|null 数据总数 */ + /** + * 数据总数 + * @var integer|null + */ protected $total; - /** @var integer 每页的数量 */ + /** + * 每页数量 + * @var integer + */ protected $listRows; - /** @var bool 是否有下一页 */ + /** + * 是否有下一页 + * @var bool + */ protected $hasMore; - /** @var array 一些配置 */ + /** + * 分页配置 + * @var array + */ protected $options = [ 'var_page' => 'page', 'path' => '/', @@ -49,9 +73,6 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J 'fragment' => '', ]; - /** @var mixed simple模式下的下个元素 */ - protected $nextItem; - public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { $this->options = array_merge($this->options, $options); @@ -68,10 +89,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J if ($simple) { $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = count($items) > ($this->listRows); - if ($this->hasMore) { - $this->nextItem = $items->slice($this->listRows, 1); - } - $items = $items->slice(0, $this->listRows); + $items = $items->slice(0, $this->listRows); } else { $this->total = $total; $this->lastPage = (int) ceil($total / $listRows); @@ -82,11 +100,12 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J } /** + * @access public * @param $items * @param $listRows * @param null $currentPage - * @param bool $simple * @param null $total + * @param bool $simple * @param array $options * @return Paginator */ @@ -107,7 +126,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 获取页码对应的链接 * - * @param $page + * @access protected + * @param $page * @return string */ protected function url($page) @@ -123,27 +143,31 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J $parameters = []; $path = str_replace('[PAGE]', $page, $this->options['path']); } + if (count($this->options['query']) > 0) { $parameters = array_merge($this->options['query'], $parameters); } + $url = $path; if (!empty($parameters)) { $url .= '?' . http_build_query($parameters, null, '&'); } + return $url . $this->buildFragment(); } /** * 自动获取当前页码 - * @param string $varPage - * @param int $default + * @access public + * @param string $varPage + * @param int $default * @return int */ public static function getCurrentPage($varPage = 'page', $default = 1) { - $page = (int) Request::instance()->param($varPage); + $page = Container::get('request')->param($varPage); - if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) { + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { return $page; } @@ -152,11 +176,12 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 自动获取当前的path + * @access public * @return string */ public static function getCurrentPath() { - return Request::instance()->baseUrl(); + return Container::get('request')->baseUrl(); } public function total() @@ -164,6 +189,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J if ($this->simple) { throw new \DomainException('not support total'); } + return $this->total; } @@ -182,11 +208,13 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J if ($this->simple) { throw new \DomainException('not support last'); } + return $this->lastPage; } /** * 数据是否足够分页 + * @access public * @return boolean */ public function hasPages() @@ -197,6 +225,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 创建一组分页链接 * + * @access public * @param int $start * @param int $end * @return array @@ -215,18 +244,21 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 设置URL锚点 * + * @access public * @param string|null $fragment * @return $this */ public function fragment($fragment) { $this->options['fragment'] = $fragment; + return $this; } /** * 添加URL参数 * + * @access public * @param array|string $key * @param string|null $value * @return $this @@ -251,6 +283,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 构造锚点字符串 * + * @access public * @return string */ protected function buildFragment() @@ -260,6 +293,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 渲染分页html + * @access public * @return mixed */ abstract public function render(); @@ -282,6 +316,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * 给每个元素执行个回调 * + * @access public * @param callable $callback * @return $this */ @@ -289,6 +324,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J { foreach ($this->items as $key => $item) { $result = $callback($item, $key); + if (false === $result) { break; } elseif (!is_object($item)) { @@ -301,6 +337,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Retrieve an external iterator + * @access public * @return Traversable An instance of an object implementing Iterator or * Traversable */ @@ -311,7 +348,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Whether a offset exists - * @param mixed $offset + * @access public + * @param mixed $offset * @return bool */ public function offsetExists($offset) @@ -321,7 +359,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Offset to retrieve - * @param mixed $offset + * @access public + * @param mixed $offset * @return mixed */ public function offsetGet($offset) @@ -331,8 +370,9 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Offset to set - * @param mixed $offset - * @param mixed $value + * @access public + * @param mixed $offset + * @param mixed $value */ public function offsetSet($offset, $value) { @@ -341,9 +381,10 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J /** * Offset to unset - * @param mixed $offset + * @access public + * @param mixed $offset * @return void - * @since 5.0.0 + * @since 5.0.0 */ public function offsetUnset($offset) { @@ -365,24 +406,19 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J public function toArray() { - if ($this->simple) { - return [ - 'per_page' => $this->listRows, - 'current_page' => $this->currentPage, - 'has_more' => $this->hasMore, - 'next_item' => $this->nextItem, - 'data' => $this->items->toArray(), - ]; - } else { - return [ - 'total' => $this->total, - 'per_page' => $this->listRows, - 'current_page' => $this->currentPage, - 'last_page' => $this->lastPage, - 'data' => $this->items->toArray(), - ]; + try { + $total = $this->total(); + } catch (\DomainException $e) { + $total = null; } + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; } /** diff --git a/Server/thinkphp/library/think/Process.php b/Server/thinkphp/library/think/Process.php index 6f3faa31..3b574db4 100644 --- a/Server/thinkphp/library/think/Process.php +++ b/Server/thinkphp/library/think/Process.php @@ -114,12 +114,13 @@ class Process /** * 构造方法 - * @param string $commandline 指令 - * @param string|null $cwd 工作目录 - * @param array|null $env 环境变量 - * @param string|null $input 输入 - * @param int|float|null $timeout 超时时间 - * @param array $options proc_open的选项 + * @access public + * @param string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 * @throws \RuntimeException * @api */ @@ -132,7 +133,7 @@ class Process $this->commandline = $commandline; $this->cwd = $cwd; - if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) { + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { $this->cwd = getcwd(); } if (null !== $env) { @@ -141,10 +142,10 @@ class Process $this->input = $input; $this->setTimeout($timeout); - $this->useFileHandles = '\\' === DS; + $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR; $this->pty = false; $this->enhanceWindowsCompatibility = true; - $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); + $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); $this->options = array_replace([ 'suppress_errors' => true, 'binary_pipes' => true, @@ -163,7 +164,8 @@ class Process /** * 运行指令 - * @param callback|null $callback + * @access public + * @param callback|null $callback * @return int */ public function run($callback = null) @@ -175,7 +177,8 @@ class Process /** * 运行指令 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @return self * @throws \RuntimeException * @throws ProcessFailedException @@ -195,7 +198,8 @@ class Process /** * 启动进程并写到 STDIN 输入后返回。 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @throws \RuntimeException * @throws \RuntimeException * @throws \LogicException @@ -216,7 +220,7 @@ class Process $commandline = $this->commandline; - if ('\\' === DS && $this->enhanceWindowsCompatibility) { + if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; foreach ($this->processPipes->getFiles() as $offset => $filename) { $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); @@ -245,7 +249,8 @@ class Process /** * 重启进程 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @return Process * @throws \RuntimeException * @throws \RuntimeException @@ -264,7 +269,8 @@ class Process /** * 等待要终止的进程 - * @param callable|null $callback + * @access public + * @param callable|null $callback * @return int */ public function wait($callback = null) @@ -278,8 +284,8 @@ class Process do { $this->checkTimeout(); - $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen(); - $close = '\\' !== DS || !$running; + $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DIRECTORY_SEPARATOR || !$running; $this->readPipes(true, $close); } while ($running); @@ -296,6 +302,7 @@ class Process /** * 获取PID + * @access public * @return int|null * @throws \RuntimeException */ @@ -312,7 +319,8 @@ class Process /** * 将一个 POSIX 信号发送到进程中 - * @param int $signal + * @access public + * @param int $signal * @return Process */ public function signal($signal) @@ -324,6 +332,7 @@ class Process /** * 禁用从底层过程获取输出和错误输出。 + * @access public * @return Process */ public function disableOutput() @@ -342,6 +351,7 @@ class Process /** * 开启从底层过程获取输出和错误输出。 + * @access public * @return Process * @throws \RuntimeException */ @@ -358,6 +368,7 @@ class Process /** * 输出是否禁用 + * @access public * @return bool */ public function isOutputDisabled() @@ -367,9 +378,9 @@ class Process /** * 获取当前的输出管道 + * @access public * @return string * @throws \LogicException - * @throws \LogicException * @api */ public function getOutput() @@ -380,13 +391,14 @@ class Process $this->requireProcessIsStarted(__FUNCTION__); - $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); return $this->stdout; } /** * 以增量方式返回的输出结果。 + * @access public * @return string */ public function getIncrementalOutput() @@ -408,6 +420,7 @@ class Process /** * 清空输出 + * @access public * @return Process */ public function clearOutput() @@ -420,6 +433,7 @@ class Process /** * 返回当前的错误输出的过程 (STDERR)。 + * @access public * @return string */ public function getErrorOutput() @@ -430,13 +444,14 @@ class Process $this->requireProcessIsStarted(__FUNCTION__); - $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); return $this->stderr; } /** * 以增量方式返回 errorOutput + * @access public * @return string */ public function getIncrementalErrorOutput() @@ -458,6 +473,7 @@ class Process /** * 清空 errorOutput + * @access public * @return Process */ public function clearErrorOutput() @@ -470,6 +486,7 @@ class Process /** * 获取退出码 + * @access public * @return null|int */ public function getExitCode() @@ -485,6 +502,7 @@ class Process /** * 获取退出文本 + * @access public * @return null|string */ public function getExitCodeText() @@ -498,6 +516,7 @@ class Process /** * 检查是否成功 + * @access public * @return bool */ public function isSuccessful() @@ -507,6 +526,7 @@ class Process /** * 是否未捕获的信号已被终止子进程 + * @access public * @return bool */ public function hasBeenSignaled() @@ -524,6 +544,7 @@ class Process /** * 返回导致子进程终止其执行的数。 + * @access public * @return int */ public function getTermSignal() @@ -541,6 +562,7 @@ class Process /** * 检查子进程信号是否已停止 + * @access public * @return bool */ public function hasBeenStopped() @@ -554,6 +576,7 @@ class Process /** * 返回导致子进程停止其执行的数。 + * @access public * @return int */ public function getStopSignal() @@ -567,6 +590,7 @@ class Process /** * 检查是否正在运行 + * @access public * @return bool */ public function isRunning() @@ -582,6 +606,7 @@ class Process /** * 检查是否已开始 + * @access public * @return bool */ public function isStarted() @@ -591,6 +616,7 @@ class Process /** * 检查是否已终止 + * @access public * @return bool */ public function isTerminated() @@ -602,6 +628,7 @@ class Process /** * 获取当前的状态 + * @access public * @return string */ public function getStatus() @@ -613,11 +640,12 @@ class Process /** * 终止进程 + * @access public */ public function stop() { if ($this->isRunning()) { - if ('\\' === DS && !$this->isSigchildEnabled()) { + if ('\\' === DIRECTORY_SEPARATOR && !$this->isSigchildEnabled()) { exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); if ($exitCode > 0) { throw new \RuntimeException('Unable to kill the process'); @@ -642,6 +670,7 @@ class Process /** * 添加一行输出 + * @access public * @param string $line */ public function addOutput($line) @@ -652,6 +681,7 @@ class Process /** * 添加一行错误输出 + * @access public * @param string $line */ public function addErrorOutput($line) @@ -662,6 +692,7 @@ class Process /** * 获取被执行的指令 + * @access public * @return string */ public function getCommandLine() @@ -671,6 +702,7 @@ class Process /** * 设置指令 + * @access public * @param string $commandline * @return self */ @@ -683,6 +715,7 @@ class Process /** * 获取超时时间 + * @access public * @return float|null */ public function getTimeout() @@ -692,6 +725,7 @@ class Process /** * 获取idle超时时间 + * @access public * @return float|null */ public function getIdleTimeout() @@ -701,7 +735,8 @@ class Process /** * 设置超时时间 - * @param int|float|null $timeout + * @access public + * @param int|float|null $timeout * @return self */ public function setTimeout($timeout) @@ -713,7 +748,8 @@ class Process /** * 设置idle超时时间 - * @param int|float|null $timeout + * @access public + * @param int|float|null $timeout * @return self */ public function setIdleTimeout($timeout) @@ -729,12 +765,13 @@ class Process /** * 设置TTY - * @param bool $tty + * @access public + * @param bool $tty * @return self */ public function setTty($tty) { - if ('\\' === DS && $tty) { + if ('\\' === DIRECTORY_SEPARATOR && $tty) { throw new \RuntimeException('TTY mode is not supported on Windows platform.'); } if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { @@ -748,6 +785,7 @@ class Process /** * 检查是否是tty模式 + * @access public * @return bool */ public function isTty() @@ -757,7 +795,8 @@ class Process /** * 设置pty模式 - * @param bool $bool + * @access public + * @param bool $bool * @return self */ public function setPty($bool) @@ -769,6 +808,7 @@ class Process /** * 是否是pty模式 + * @access public * @return bool */ public function isPty() @@ -778,6 +818,7 @@ class Process /** * 获取工作目录 + * @access public * @return string|null */ public function getWorkingDirectory() @@ -791,7 +832,8 @@ class Process /** * 设置工作目录 - * @param string $cwd + * @access public + * @param string $cwd * @return self */ public function setWorkingDirectory($cwd) @@ -803,6 +845,7 @@ class Process /** * 获取环境变量 + * @access public * @return array */ public function getEnv() @@ -812,7 +855,8 @@ class Process /** * 设置环境变量 - * @param array $env + * @access public + * @param array $env * @return self */ public function setEnv(array $env) @@ -831,6 +875,7 @@ class Process /** * 获取输入 + * @access public * @return null|string */ public function getInput() @@ -840,7 +885,8 @@ class Process /** * 设置输入 - * @param mixed $input + * @access public + * @param mixed $input * @return self */ public function setInput($input) @@ -856,6 +902,7 @@ class Process /** * 获取proc_open的选项 + * @access public * @return array */ public function getOptions() @@ -865,7 +912,8 @@ class Process /** * 设置proc_open的选项 - * @param array $options + * @access public + * @param array $options * @return self */ public function setOptions(array $options) @@ -877,6 +925,7 @@ class Process /** * 是否兼容windows + * @access public * @return bool */ public function getEnhanceWindowsCompatibility() @@ -886,7 +935,8 @@ class Process /** * 设置是否兼容windows - * @param bool $enhance + * @access public + * @param bool $enhance * @return self */ public function setEnhanceWindowsCompatibility($enhance) @@ -898,6 +948,7 @@ class Process /** * 返回是否 sigchild 兼容模式激活 + * @access public * @return bool */ public function getEnhanceSigchildCompatibility() @@ -907,7 +958,8 @@ class Process /** * 激活 sigchild 兼容性模式。 - * @param bool $enhance + * @access public + * @param bool $enhance * @return self */ public function setEnhanceSigchildCompatibility($enhance) @@ -941,6 +993,7 @@ class Process /** * 是否支持pty + * @access public * @return bool */ public static function isPtySupported() @@ -951,7 +1004,7 @@ class Process return $result; } - if ('\\' === DS) { + if ('\\' === DIRECTORY_SEPARATOR) { return $result = false; } @@ -967,11 +1020,12 @@ class Process /** * 创建所需的 proc_open 的描述符 + * @access private * @return array */ private function getDescriptors() { - if ('\\' === DS) { + if ('\\' === DIRECTORY_SEPARATOR) { $this->processPipes = WindowsPipes::create($this, $this->input); } else { $this->processPipes = UnixPipes::create($this, $this->input); @@ -990,7 +1044,8 @@ class Process /** * 建立 wait () 使用的回调。 - * @param callable|null $callback + * @access protected + * @param callable|null $callback * @return callable */ protected function buildCallback($callback) @@ -1013,6 +1068,7 @@ class Process /** * 更新状态 + * @access protected * @param bool $blocking */ protected function updateStatus($blocking) @@ -1024,7 +1080,7 @@ class Process $this->processInformation = proc_get_status($this->process); $this->captureExitCode(); - $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true); + $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); if (!$this->processInformation['running']) { $this->close(); @@ -1033,6 +1089,7 @@ class Process /** * 是否开启 '--enable-sigchild' + * @access protected * @return bool */ protected function isSigchildEnabled() @@ -1053,7 +1110,8 @@ class Process /** * 验证是否超时 - * @param int|float|null $timeout + * @access private + * @param int|float|null $timeout * @return float|null */ private function validateTimeout($timeout) @@ -1071,8 +1129,9 @@ class Process /** * 读取pipes - * @param bool $blocking - * @param bool $close + * @access private + * @param bool $blocking + * @param bool $close */ private function readPipes($blocking, $close) { @@ -1100,6 +1159,7 @@ class Process /** * 关闭资源 + * @access private * @return int 退出码 */ private function close() @@ -1146,8 +1206,9 @@ class Process /** * 将一个 POSIX 信号发送到进程中。 - * @param int $signal - * @param bool $throwException + * @access private + * @param int $signal + * @param bool $throwException * @return bool */ private function doSignal($signal, $throwException) @@ -1183,7 +1244,8 @@ class Process /** * 确保进程已经开启 - * @param string $functionName + * @access private + * @param string $functionName */ private function requireProcessIsStarted($functionName) { @@ -1194,7 +1256,8 @@ class Process /** * 确保进程已经终止 - * @param string $functionName + * @access private + * @param string $functionName */ private function requireProcessIsTerminated($functionName) { diff --git a/Server/thinkphp/library/think/Request.php b/Server/thinkphp/library/think/Request.php index 5997a763..6b6dd4b4 100644 --- a/Server/thinkphp/library/think/Request.php +++ b/Server/thinkphp/library/think/Request.php @@ -11,86 +11,215 @@ namespace think; +use think\facade\Cookie; +use think\facade\Session; + class Request { /** - * @var object 对象实例 + * 配置参数 + * @var array */ - protected static $instance; + protected $config = [ + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'HTTP_X_REAL_IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + ]; - protected $method; /** - * @var string 域名(含协议和端口) + * 请求类型 + * @var string + */ + protected $method; + + /** + * 主机名(含端口) + * @var string + */ + protected $host; + + /** + * 域名(含协议及端口) + * @var string */ protected $domain; /** - * @var string URL地址 + * 子域名 + * @var string + */ + protected $subDomain; + + /** + * 泛域名 + * @var string + */ + protected $panDomain; + + /** + * 当前URL地址 + * @var string */ protected $url; /** - * @var string 基础URL + * 基础URL + * @var string */ protected $baseUrl; /** - * @var string 当前执行的文件 + * 当前执行的文件 + * @var string */ protected $baseFile; /** - * @var string 访问的ROOT地址 + * 访问的ROOT地址 + * @var string */ protected $root; /** - * @var string pathinfo + * pathinfo + * @var string */ protected $pathinfo; /** - * @var string pathinfo(不含后缀) + * pathinfo(不含后缀) + * @var string */ protected $path; /** - * @var array 当前路由信息 + * 当前路由信息 + * @var array */ protected $routeInfo = []; /** - * @var array 环境变量 + * 当前调度信息 + * @var \think\route\Dispatch */ - protected $env; + protected $dispatch; /** - * @var array 当前调度信息 + * 当前模块名 + * @var string */ - protected $dispatch = []; protected $module; + + /** + * 当前控制器名 + * @var string + */ protected $controller; + + /** + * 当前操作名 + * @var string + */ protected $action; - // 当前语言集 + + /** + * 当前语言集 + * @var string + */ protected $langset; /** - * @var array 请求参数 + * 当前请求参数 + * @var array */ - protected $param = []; - protected $get = []; - protected $post = []; - protected $request = []; - protected $route = []; - protected $put; - protected $session = []; - protected $file = []; - protected $cookie = []; - protected $server = []; - protected $header = []; + protected $param = []; /** - * @var array 资源类型 + * 当前GET参数 + * @var array + */ + protected $get = []; + + /** + * 当前POST参数 + * @var array + */ + protected $post = []; + + /** + * 当前REQUEST参数 + * @var array + */ + protected $request = []; + + /** + * 当前ROUTE参数 + * @var array + */ + protected $route = []; + + /** + * 当前PUT参数 + * @var array + */ + protected $put; + + /** + * 当前SESSION参数 + * @var array + */ + protected $session = []; + + /** + * 当前FILE参数 + * @var array + */ + protected $file = []; + + /** + * 当前COOKIE参数 + * @var array + */ + protected $cookie = []; + + /** + * 当前SERVER参数 + * @var array + */ + protected $server = []; + + /** + * 当前ENV参数 + * @var array + */ + protected $env = []; + + /** + * 当前HEADER参数 + * @var array + */ + protected $header = []; + + /** + * 资源类型定义 + * @var array */ protected $mimeType = [ 'xml' => 'application/xml,text/xml,application/x-xml', @@ -107,20 +236,48 @@ class Request 'html' => 'text/html,application/xhtml+xml,*/*', ]; + /** + * 当前请求内容 + * @var string + */ protected $content; - // 全局过滤规则 + /** + * 全局过滤规则 + * @var array + */ protected $filter; - // Hook扩展方法 - protected static $hook = []; - // 绑定的属性 - protected $bind = []; - // php://input + + /** + * 扩展方法 + * @var array + */ + protected $hook = []; + + /** + * php://input内容 + * @var string + */ protected $input; - // 请求缓存 + + /** + * 请求缓存 + * @var array + */ protected $cache; - // 缓存是否检查 + + /** + * 缓存是否检查 + * @var bool + */ protected $isCheckCache; + + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + /** * 是否合并Param * @var bool @@ -128,98 +285,94 @@ class Request protected $mergeParam = false; /** - * 构造函数 - * @access protected - * @param array $options 参数 + * 架构函数 + * @access public + * @param array $options 参数 */ - protected function __construct($options = []) + public function __construct(array $options = []) { - foreach ($options as $name => $item) { - if (property_exists($this, $name)) { - $this->$name = $item; - } - } - if (is_null($this->filter)) { - $this->filter = Config::get('default_filter'); - } + $this->init($options); // 保存 php://input $this->input = file_get_contents('php://input'); } + public function init(array $options = []) + { + $this->config = array_merge($this->config, $options); + + if (is_null($this->filter) && !empty($this->config['default_filter'])) { + $this->filter = $this->config['default_filter']; + } + } + + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + return isset($this->config[$name]) ? $this->config[$name] : null; + } + + public static function __make(App $app, Config $config) + { + $request = new static($config->pull('app')); + + $request->server = $_SERVER; + $request->env = $app['env']->get(); + + return $request; + } + public function __call($method, $args) { - if (array_key_exists($method, self::$hook)) { + if (array_key_exists($method, $this->hook)) { array_unshift($args, $this); - return call_user_func_array(self::$hook[$method], $args); - } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + return call_user_func_array($this->hook[$method], $args); } + + throw new Exception('method not exists:' . static::class . '->' . $method); } /** * Hook 方法注入 * @access public - * @param string|array $method 方法名 - * @param mixed $callback callable + * @param string|array $method 方法名 + * @param mixed $callback callable * @return void */ - public static function hook($method, $callback = null) + public function hook($method, $callback = null) { if (is_array($method)) { - self::$hook = array_merge(self::$hook, $method); + $this->hook = array_merge($this->hook, $method); } else { - self::$hook[$method] = $callback; - } - } - - /** - * 初始化 - * @access public - * @param array $options 参数 - * @return \think\Request - */ - public static function instance($options = []) - { - if (is_null(self::$instance)) { - self::$instance = new static($options); - } - return self::$instance; - } - - /** - * 销毁当前请求对象 - * @access public - * @return void - */ - public static function destroy() - { - if (!is_null(self::$instance)) { - self::$instance = null; + $this->hook[$method] = $callback; } } /** * 创建一个URL请求 * @access public - * @param string $uri URL地址 - * @param string $method 请求类型 - * @param array $params 请求参数 - * @param array $cookie - * @param array $files - * @param array $server - * @param string $content + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content * @return \think\Request */ - public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + public function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) { $server['PATH_INFO'] = ''; $server['REQUEST_METHOD'] = strtoupper($method); $info = parse_url($uri); + if (isset($info['host'])) { $server['SERVER_NAME'] = $info['host']; $server['HTTP_HOST'] = $info['host']; } + if (isset($info['scheme'])) { if ('https' === $info['scheme']) { $server['HTTPS'] = 'on'; @@ -229,22 +382,29 @@ class Request $server['SERVER_PORT'] = 80; } } + if (isset($info['port'])) { $server['SERVER_PORT'] = $info['port']; $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; } + if (isset($info['user'])) { $server['PHP_AUTH_USER'] = $info['user']; } + if (isset($info['pass'])) { $server['PHP_AUTH_PW'] = $info['pass']; } + if (!isset($info['path'])) { $info['path'] = '/'; } - $options = []; + + $options = []; + $queryString = ''; + $options[strtolower($method)] = $params; - $queryString = ''; + if (isset($info['query'])) { parse_str(html_entity_decode($info['query']), $query); if (!empty($params)) { @@ -257,6 +417,7 @@ class Request } elseif (!empty($params)) { $queryString = http_build_query($params, '', '&'); } + if ($queryString) { parse_str($queryString, $get); $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; @@ -274,123 +435,239 @@ class Request $options['method'] = $server['REQUEST_METHOD']; $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; $options['content'] = $content; - self::$instance = new self($options); - return self::$instance; - } - /** - * 设置或获取当前包含协议的域名 - * @access public - * @param string $domain 域名 - * @return string - */ - public function domain($domain = null) - { - if (!is_null($domain)) { - $this->domain = $domain; - return $this; - } elseif (!$this->domain) { - $this->domain = $this->scheme() . '://' . $this->host(); + $request = new static(); + foreach ($options as $name => $item) { + if (property_exists($request, $name)) { + $request->$name = $item; + } } - return $this->domain; + + return $request; } /** - * 设置或获取当前完整URL 包括QUERY_STRING + * 获取当前包含协议、端口的域名 * @access public - * @param string|true $url URL地址 true 带域名获取 + * @param bool $port 是否需要去除端口号 * @return string */ - public function url($url = null) + public function domain($port = false) { - if (!is_null($url) && true !== $url) { - $this->url = $url; - return $this; - } elseif (!$this->url) { - if (IS_CLI) { + return $this->scheme() . '://' . $this->host($port); + } + + /** + * 获取当前根域名 + * @access public + * @return string + */ + public function rootDomain() + { + $root = $this->config['url_domain_root']; + + if (!$root) { + $item = explode('.', $this->host(true)); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + + return $root; + } + + /** + * 获取当前子域名 + * @access public + * @return string + */ + public function subDomain() + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->config['url_domain_root']; + + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($this->host(true), $rootDomain, true), '.')); + } else { + $domain = explode('.', $this->host(true), -2); + } + + $this->subDomain = implode('.', $domain); + } + + return $this->subDomain; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setPanDomain($domain) + { + $this->panDomain = $domain; + return $this; + } + + /** + * 获取当前泛域名的值 + * @access public + * @return string + */ + public function panDomain() + { + return $this->panDomain; + } + + /** + * 设置当前完整URL 包括QUERY_STRING + * @access public + * @param string $url URL + * @return $this + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * 获取当前完整URL 包括QUERY_STRING + * @access public + * @param bool $complete 是否包含域名 + * @return string + */ + public function url($complete = false) + { + if (!$this->url) { + if ($this->isCli()) { $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; - } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { - $this->url = $_SERVER['HTTP_X_REWRITE_URL']; - } elseif (isset($_SERVER['REQUEST_URI'])) { - $this->url = $_SERVER['REQUEST_URI']; - } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { - $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $this->url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $this->url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $this->url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); } else { $this->url = ''; } } - return true === $url ? $this->domain() . $this->url : $this->url; + + return $complete ? $this->domain() . $this->url : $this->url; } /** - * 设置或获取当前URL 不含QUERY_STRING + * 设置当前完整URL 不包括QUERY_STRING * @access public - * @param string $url URL地址 - * @return string + * @param string $url URL + * @return $this */ - public function baseUrl($url = null) + public function setBaseUrl($url) { - if (!is_null($url) && true !== $url) { - $this->baseUrl = $url; - return $this; - } elseif (!$this->baseUrl) { + $this->baseUrl = $url; + return $this; + } + + /** + * 获取当前URL 不含QUERY_STRING + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function baseUrl($domain = false) + { + if (!$this->baseUrl) { $str = $this->url(); $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; } - return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; + + return $domain ? $this->domain() . $this->baseUrl : $this->baseUrl; } /** * 设置或获取当前执行的文件 SCRIPT_NAME * @access public - * @param string $file 当前执行的文件 - * @return string + * @param bool $domain 是否包含域名 + * @return string|$this */ - public function baseFile($file = null) + public function baseFile($domain = false) { - if (!is_null($file) && true !== $file) { - $this->baseFile = $file; - return $this; - } elseif (!$this->baseFile) { + if (!$this->baseFile) { $url = ''; - if (!IS_CLI) { - $script_name = basename($_SERVER['SCRIPT_FILENAME']); - if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['SCRIPT_NAME']; - } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { - $url = $_SERVER['PHP_SELF']; - } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['ORIG_SCRIPT_NAME']; - } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { - $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; - } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { - $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); + if (!$this->isCli()) { + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); } } $this->baseFile = $url; } - return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; + + return $domain ? $this->domain() . $this->baseFile : $this->baseFile; } /** - * 设置或获取URL访问根地址 + * 设置URL访问根地址 * @access public - * @param string $url URL地址 - * @return string + * @param string $url URL地址 + * @return string|$this */ - public function root($url = null) + public function setRoot($url = null) { - if (!is_null($url) && true !== $url) { - $this->root = $url; - return $this; - } elseif (!$this->root) { + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function root($domain = false) + { + if (!$this->root) { $file = $this->baseFile(); if ($file && 0 !== strpos($this->url(), $file)) { $file = str_replace('\\', '/', dirname($file)); } $this->root = rtrim($file, '/'); } - return true === $url ? $this->domain() . $this->root : $this->root; + + return $domain ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取URL访问根目录 + * @access public + * @return string + */ + public function rootUrl() + { + $base = $this->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base; + + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + + return $root; + } + + public function setPathinfo($pathinfo) + { + $this->pathinfo = $pathinfo; + return $this; } /** @@ -401,27 +678,38 @@ class Request public function pathinfo() { if (is_null($this->pathinfo)) { - if (isset($_GET[Config::get('var_pathinfo')])) { + if (isset($_GET[$this->config['var_pathinfo']])) { // 判断URL里面是否有兼容模式参数 - $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; - unset($_GET[Config::get('var_pathinfo')]); - } elseif (IS_CLI) { + $pathinfo = $_GET[$this->config['var_pathinfo']]; + unset($_GET[$this->config['var_pathinfo']]); + unset($this->get[$this->config['var_pathinfo']]); + } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... - $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + $pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif ('cli-server' == PHP_SAPI) { + $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); + } elseif ($this->server('PATH_INFO')) { + $pathinfo = $this->server('PATH_INFO'); } // 分析PATHINFO信息 - if (!isset($_SERVER['PATH_INFO'])) { - foreach (Config::get('pathinfo_fetch') as $type) { - if (!empty($_SERVER[$type])) { - $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? - substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + if (!isset($pathinfo)) { + foreach ($this->config['pathinfo_fetch'] as $type) { + if ($this->server($type)) { + $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ? + substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type); break; } } } - $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + + if (!empty($pathinfo)) { + unset($this->get[$pathinfo], $this->request[$pathinfo]); + } + + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); } + return $this->pathinfo; } @@ -433,8 +721,9 @@ class Request public function path() { if (is_null($this->path)) { - $suffix = Config::get('url_html_suffix'); + $suffix = $this->config['url_html_suffix']; $pathinfo = $this->pathinfo(); + if (false === $suffix) { // 禁止伪静态访问 $this->path = $pathinfo; @@ -446,6 +735,7 @@ class Request $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); } } + return $this->path; } @@ -462,12 +752,12 @@ class Request /** * 获取当前请求的时间 * @access public - * @param bool $float 是否使用浮点类型 + * @param bool $float 是否使用浮点类型 * @return integer|float */ public function time($float = false) { - return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME'); } /** @@ -478,6 +768,7 @@ class Request public function type() { $accept = $this->server('HTTP_ACCEPT'); + if (empty($accept)) { return false; } @@ -490,14 +781,15 @@ class Request } } } + return false; } /** * 设置资源类型 * @access public - * @param string|array $type 资源类型名 - * @param string $val 资源类型 + * @param string|array $type 资源类型名 + * @param string $val 资源类型 * @return void */ public function mimeType($type, $val = '') @@ -512,30 +804,31 @@ class Request /** * 当前的请求类型 * @access public - * @param bool $method true 获取原始请求类型 + * @param bool $origin 是否获取原始请求类型 * @return string */ - public function method($method = false) + public function method($origin = false) { - if (true === $method) { + if ($origin) { // 获取原始请求类型 return $this->server('REQUEST_METHOD') ?: 'GET'; } elseif (!$this->method) { - if (isset($_POST[Config::get('var_method')])) { - $method = strtoupper($_POST[Config::get('var_method')]); - if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) { - $this->method = $method; - $this->{$this->method}($_POST); + if (isset($_POST[$this->config['var_method']])) { + $method = strtolower($_POST[$this->config['var_method']]); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) { + $this->method = strtoupper($method); + $this->{$method} = $_POST; } else { $this->method = 'POST'; } - unset($_POST[Config::get('var_method')]); - } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { - $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + unset($_POST[$this->config['var_method']]); + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); } else { $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; } } + return $this->method; } @@ -632,15 +925,16 @@ class Request /** * 获取当前请求的参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function param($name = '', $default = null, $filter = '') { - if (empty($this->mergeParam)) { + if (!$this->mergeParam) { $method = $this->method(true); + // 自动获取请求变量 switch ($method) { case 'POST': @@ -654,43 +948,55 @@ class Request default: $vars = []; } + // 当前请求参数和URL地址中的参数合并 - $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + $this->mergeParam = true; } + if (true === $name) { // 获取包含文件上传信息的数组 $file = $this->file(); $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + return $this->input($data, '', $default, $filter); } + return $this->input($this->param, $name, $default, $filter); } /** - * 设置获取路由参数 + * 设置路由变量 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param array $route 路由变量 + * @return $this + */ + public function setRouteVars(array $route) + { + $this->route = array_merge($this->route, $route); + return $this; + } + + /** + * 获取路由参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function route($name = '', $default = null, $filter = '') { - if (is_array($name)) { - $this->param = []; - $this->mergeParam = false; - return $this->route = array_merge($this->route, $name); - } return $this->input($this->route, $name, $default, $filter); } /** - * 设置获取GET参数 + * 获取GET参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function get($name = '', $default = null, $filter = '') @@ -698,73 +1004,62 @@ class Request if (empty($this->get)) { $this->get = $_GET; } - if (is_array($name)) { - $this->param = []; - $this->mergeParam = false; - return $this->get = array_merge($this->get, $name); - } + return $this->input($this->get, $name, $default, $filter); } /** - * 设置获取POST参数 + * 获取POST参数 * @access public - * @param string $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function post($name = '', $default = null, $filter = '') { if (empty($this->post)) { - $content = $this->input; - if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { - $this->post = (array) json_decode($content, true); - } else { - $this->post = $_POST; - } - } - if (is_array($name)) { - $this->param = []; - $this->mergeParam = false; - return $this->post = array_merge($this->post, $name); + $this->post = !empty($_POST) ? $_POST : $this->getInputData($this->input); } + return $this->input($this->post, $name, $default, $filter); } /** - * 设置获取PUT参数 + * 获取PUT参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function put($name = '', $default = null, $filter = '') { if (is_null($this->put)) { - $content = $this->input; - if (false !== strpos($this->contentType(), 'application/json')) { - $this->put = (array) json_decode($content, true); - } else { - parse_str($content, $this->put); - } - } - if (is_array($name)) { - $this->param = []; - $this->mergeParam = false; - return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); + $this->put = $this->getInputData($this->input); } return $this->input($this->put, $name, $default, $filter); } + protected function getInputData($content) + { + if (false !== strpos($this->contentType(), 'json')) { + return (array) json_decode($content, true); + } elseif (strpos($content, '=')) { + parse_str($content, $data); + return $data; + } + + return []; + } + /** - * 设置获取DELETE参数 + * 获取DELETE参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function delete($name = '', $default = null, $filter = '') @@ -773,11 +1068,11 @@ class Request } /** - * 设置获取PATCH参数 + * 获取PATCH参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function patch($name = '', $default = null, $filter = '') @@ -787,9 +1082,10 @@ class Request /** * 获取request变量 - * @param string $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function request($name = '', $default = null, $filter = '') @@ -797,39 +1093,38 @@ class Request if (empty($this->request)) { $this->request = $_REQUEST; } - if (is_array($name)) { - $this->param = []; - $this->mergeParam = false; - return $this->request = array_merge($this->request, $name); - } + return $this->input($this->request, $name, $default, $filter); } /** * 获取session数据 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string $name 数据名称 + * @param string $default 默认值 * @return mixed */ - public function session($name = '', $default = null, $filter = '') + public function session($name = '', $default = null) { if (empty($this->session)) { $this->session = Session::get(); } - if (is_array($name)) { - return $this->session = array_merge($this->session, $name); + + if ('' === $name) { + return $this->session; } - return $this->input($this->session, $name, $default, $filter); + + $data = $this->getData($this->session, $name); + + return is_null($data) ? $default : $data; } /** * 获取cookie参数 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string $name 变量名 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function cookie($name = '', $default = null, $filter = '') @@ -837,9 +1132,8 @@ class Request if (empty($this->cookie)) { $this->cookie = Cookie::get(); } - if (is_array($name)) { - return $this->cookie = array_merge($this->cookie, $name); - } elseif (!empty($name)) { + + if (!empty($name)) { $data = Cookie::has($name) ? Cookie::get($name) : $default; } else { $data = $this->cookie; @@ -854,32 +1148,32 @@ class Request } else { $this->filterValue($data, $name, $filter); } + return $data; } /** * 获取server参数 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string $name 数据名称 + * @param string $default 默认值 * @return mixed */ - public function server($name = '', $default = null, $filter = '') + public function server($name = '', $default = null) { - if (empty($this->server)) { - $this->server = $_SERVER; + if (empty($name)) { + return $this->server; + } else { + $name = strtoupper($name); } - if (is_array($name)) { - return $this->server = array_merge($this->server, $name); - } - return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); + + return isset($this->server[$name]) ? $this->server[$name] : $default; } /** * 获取上传的文件信息 * @access public - * @param string|array $name 名称 + * @param string $name 名称 * @return null|array|\think\File */ public function file($name = '') @@ -887,43 +1181,16 @@ class Request if (empty($this->file)) { $this->file = isset($_FILES) ? $_FILES : []; } - if (is_array($name)) { - return $this->file = array_merge($this->file, $name); - } + $files = $this->file; if (!empty($files)) { - // 处理上传文件 - $array = []; - foreach ($files as $key => $file) { - if (is_array($file['name'])) { - $item = []; - $keys = array_keys($file); - $count = count($file['name']); - for ($i = 0; $i < $count; $i++) { - if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { - continue; - } - $temp['key'] = $key; - foreach ($keys as $_key) { - $temp[$_key] = $file[$_key][$i]; - } - $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); - } - $array[$key] = $item; - } else { - if ($file instanceof File) { - $array[$key] = $file; - } else { - if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { - continue; - } - $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); - } - } - } if (strpos($name, '.')) { list($name, $sub) = explode('.', $name); } + + // 处理上传文件 + $array = $this->dealUploadFile($files, $name); + if ('' === $name) { // 获取全部文件 return $array; @@ -933,33 +1200,96 @@ class Request return $array[$name]; } } + return; } + protected function dealUploadFile($files, $name) + { + $array = []; + foreach ($files as $key => $file) { + if ($file instanceof File) { + $array[$key] = $file; + } elseif (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + + for ($i = 0; $i < $count; $i++) { + if ($file['error'][$i] > 0) { + if ($name == $key) { + $this->throwUploadFileError($file['error'][$i]); + } else { + continue; + } + } + + $temp['key'] = $key; + + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + + $array[$key] = $item; + } else { + if ($file['error'] > 0) { + if ($key == $name) { + $this->throwUploadFileError($file['error']); + } else { + continue; + } + } + + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + + return $array; + } + + protected function throwUploadFileError($error) + { + static $fileUploadErrors = [ + 1 => 'upload File size exceeds the maximum value', + 2 => 'upload File size exceeds the maximum value', + 3 => 'only the portion of file is uploaded', + 4 => 'no file to uploaded', + 6 => 'upload temp dir not found', + 7 => 'file write error', + ]; + + $msg = $fileUploadErrors[$error]; + + throw new Exception($msg); + } + /** * 获取环境变量 - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 * @return mixed */ - public function env($name = '', $default = null, $filter = '') + public function env($name = '', $default = null) { - if (empty($this->env)) { - $this->env = $_ENV; + if (empty($name)) { + return $this->env; + } else { + $name = strtoupper($name); } - if (is_array($name)) { - return $this->env = array_merge($this->env, $name); - } - return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); + + return isset($this->env[$name]) ? $this->env[$name] : $default; } /** - * 设置或者获取当前的Header + * 获取当前的Header * @access public - * @param string|array $name header名称 - * @param string $default 默认值 - * @return string + * @param string $name header名称 + * @param string $default 默认值 + * @return string|array */ public function header($name = '', $default = null) { @@ -968,7 +1298,7 @@ class Request if (function_exists('apache_request_headers') && $result = apache_request_headers()) { $header = $result; } else { - $server = $this->server ?: $_SERVER; + $server = $this->server; foreach ($server as $key => $val) { if (0 === strpos($key, 'HTTP_')) { $key = str_replace('_', '-', strtolower(substr($key, 5))); @@ -984,22 +1314,39 @@ class Request } $this->header = array_change_key_case($header); } - if (is_array($name)) { - return $this->header = array_merge($this->header, $name); - } + if ('' === $name) { return $this->header; } + $name = str_replace('_', '-', strtolower($name)); + return isset($this->header[$name]) ? $this->header[$name] : $default; } + /** + * 递归重置数组指针 + * @access public + * @param array $data 数据源 + * @return void + */ + public function arrayReset(array &$data) + { + foreach ($data as &$value) { + if (is_array($value)) { + $this->arrayReset($value); + } + } + reset($data); + } + /** * 获取变量 支持过滤和默认值 - * @param array $data 数据源 - * @param string|false $name 字段名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤函数 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 * @return mixed */ public function input($data = [], $name = '', $default = null, $filter = '') @@ -1008,23 +1355,20 @@ class Request // 获取原始数据 return $data; } + $name = (string) $name; if ('' != $name) { // 解析name if (strpos($name, '/')) { list($name, $type) = explode('/', $name); - } else { - $type = 's'; } - // 按.拆分成多维数组进行判断 - foreach (explode('.', $name) as $val) { - if (isset($data[$val])) { - $data = $data[$val]; - } else { - // 无输入数据,返回默认值 - return $default; - } + + $data = $this->getData($data, $name); + + if (is_null($data)) { + return $default; } + if (is_object($data)) { return $data; } @@ -1035,7 +1379,10 @@ class Request if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); - reset($data); + if (version_compare(PHP_VERSION, '7.1.0', '<')) { + // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针 + $this->arrayReset($data); + } } else { $this->filterValue($data, $name, $filter); } @@ -1044,21 +1391,43 @@ class Request // 强制类型转换 $this->typeCast($data, $type); } + + return $data; + } + + /** + * 获取数据 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @return mixed + */ + protected function getData(array $data, $name) + { + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + return; + } + } + return $data; } /** * 设置或获取当前的过滤规则 - * @param mixed $filter 过滤规则 + * @access public + * @param mixed $filter 过滤规则 * @return mixed */ public function filter($filter = null) { if (is_null($filter)) { return $this->filter; - } else { - $this->filter = $filter; } + + $this->filter = $filter; } protected function getFilter($filter, $default) @@ -1075,19 +1444,22 @@ class Request } $filter[] = $default; + return $filter; } /** * 递归过滤给定的值 - * @param mixed $value 键值 - * @param mixed $key 键名 - * @param array $filters 过滤方法+默认值 + * @access public + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 * @return mixed */ private function filterValue(&$value, $key, $filters) { $default = array_pop($filters); + foreach ($filters as $filter) { if (is_callable($filter)) { // 调用函数或者方法过滤 @@ -1111,27 +1483,15 @@ class Request } } } - return $this->filterExp($value); - } - /** - * 过滤表单中的表达式 - * @param string $value - * @return void - */ - public function filterExp(&$value) - { - // 过滤查询特殊字符 - 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)) { - $value .= ' '; - } - // TODO 其他安全过滤 + return $value; } /** * 强制类型转换 - * @param string $data - * @param string $type + * @access public + * @param string $data + * @param string $type * @return mixed */ private function typeCast(&$data, $type) @@ -1155,30 +1515,35 @@ class Request break; // 字符串 case 's': - default: if (is_scalar($data)) { $data = (string) $data; } else { throw new \InvalidArgumentException('variable type error:' . gettype($data)); } + break; } } /** * 是否存在某个请求参数 * @access public - * @param string $name 变量名 - * @param string $type 变量类型 - * @param bool $checkEmpty 是否检测空值 + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 * @return mixed */ public function has($name, $type = 'param', $checkEmpty = false) { + if (!in_array($type, ['param', 'get', 'post', 'request', 'put', 'patch', 'file', 'session', 'cookie', 'env', 'header', 'route'])) { + return false; + } + if (empty($this->$type)) { $param = $this->$type(); } else { $param = $this->$type; } + // 按.拆分成多维数组进行判断 foreach (explode('.', $name) as $val) { if (isset($param[$val])) { @@ -1187,36 +1552,50 @@ class Request return false; } } + return ($checkEmpty && '' === $param) ? false : true; } /** * 获取指定的参数 * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 + * @param string|array $name 变量名 + * @param string $type 变量类型 * @return mixed */ public function only($name, $type = 'param') { $param = $this->$type(); + if (is_string($name)) { $name = explode(',', $name); } + $item = []; - foreach ($name as $key) { + foreach ($name as $key => $val) { + + if (is_int($key)) { + $default = null; + $key = $val; + } else { + $default = $val; + } + if (isset($param[$key])) { $item[$key] = $param[$key]; + } elseif (isset($default)) { + $item[$key] = $default; } } + return $item; } /** * 排除指定参数获取 * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 + * @param string|array $name 变量名 + * @param string $type 变量类型 * @return mixed */ public function except($name, $type = 'param') @@ -1225,11 +1604,13 @@ class Request if (is_string($name)) { $name = explode(',', $name); } + foreach ($name as $key) { if (isset($param[$key])) { unset($param[$key]); } } + return $param; } @@ -1240,95 +1621,120 @@ class Request */ public function isSsl() { - $server = array_merge($_SERVER, $this->server); - if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { return true; - } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { + } elseif ('https' == $this->server('REQUEST_SCHEME')) { return true; - } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { + } elseif ('443' == $this->server('SERVER_PORT')) { return true; - } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { return true; - } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) { + } elseif ($this->config['https_agent_name'] && $this->server($this->config['https_agent_name'])) { return true; } + return false; } + /** + * 当前是否JSON请求 + * @access public + * @return bool + */ + public function isJson() + { + return false !== strpos($this->type(), 'json'); + } + /** * 当前是否Ajax请求 * @access public - * @param bool $ajax true 获取原始ajax请求 + * @param bool $ajax true 获取原始ajax请求 * @return bool */ public function isAjax($ajax = false) { - $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); - $result = ('xmlhttprequest' == $value) ? true : false; + $value = $this->server('HTTP_X_REQUESTED_WITH'); + $result = 'xmlhttprequest' == strtolower($value) ? true : false; + if (true === $ajax) { return $result; - } else { - $result = $this->param(Config::get('var_ajax')) ? true : $result; - $this->mergeParam = false; - return $result; } + + $result = $this->param($this->config['var_ajax']) ? true : $result; + $this->mergeParam = false; + return $result; } /** * 当前是否Pjax请求 * @access public - * @param bool $pjax true 获取原始pjax请求 + * @param bool $pjax true 获取原始pjax请求 * @return bool */ public function isPjax($pjax = false) { $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + if (true === $pjax) { return $result; - } else { - $result = $this->param(Config::get('var_pjax')) ? true : $result; - $this->mergeParam = false; - return $result; } + + $result = $this->param($this->config['var_pjax']) ? true : $result; + $this->mergeParam = false; + return $result; } /** * 获取客户端IP地址 - * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 - * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @access public + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) * @return mixed */ public function ip($type = 0, $adv = true) { $type = $type ? 1 : 0; static $ip = null; + if (null !== $ip) { return $ip[$type]; } - $httpAgentIp = Config::get('http_agent_ip'); + $httpAgentIp = $this->config['http_agent_ip']; - if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { - $ip = $_SERVER[$httpAgentIp]; + if ($httpAgentIp && $this->server($httpAgentIp)) { + $ip = $this->server($httpAgentIp); } elseif ($adv) { - if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + if ($this->server('HTTP_X_FORWARDED_FOR')) { + $arr = explode(',', $this->server('HTTP_X_FORWARDED_FOR')); $pos = array_search('unknown', $arr); if (false !== $pos) { unset($arr[$pos]); } $ip = trim(current($arr)); - } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { - $ip = $_SERVER['HTTP_CLIENT_IP']; - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; + } elseif ($this->server('HTTP_CLIENT_IP')) { + $ip = $this->server('HTTP_CLIENT_IP'); + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); } - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); } + + // IP地址类型 + $ip_mode = (strpos($ip, ':') === false) ? 'ipv4' : 'ipv6'; + // IP地址合法验证 - $long = sprintf("%u", ip2long($ip)); - $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + if (filter_var($ip, FILTER_VALIDATE_IP) !== $ip) { + $ip = ('ipv4' === $ip_mode) ? '0.0.0.0' : '::'; + } + + // 如果是ipv4地址,则直接使用ip2long返回int类型ip;如果是ipv6地址,暂时不支持,直接返回0 + $long_ip = ('ipv4' === $ip_mode) ? sprintf("%u", ip2long($ip)) : 0; + + $ip = [$ip, $long_ip]; + return $ip[$type]; } @@ -1339,17 +1745,17 @@ class Request */ public function isMobile() { - if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) { return true; - } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) { return true; - } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { return true; - } 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'])) { + } elseif ($this->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', $this->server('HTTP_USER_AGENT'))) { return true; - } else { - return false; } + + return false; } /** @@ -1372,21 +1778,32 @@ class Request return $this->server('QUERY_STRING'); } + /** + * 设置当前请求的host(包含端口) + * @access public + * @param string $host 主机名(含端口) + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + /** * 当前请求的host * @access public - * @param bool $strict true 仅仅获取HOST + * @param bool $strict true 仅仅获取HOST * @return string */ public function host($strict = false) { - if (isset($_SERVER['HTTP_X_REAL_HOST'])) { - $host = $_SERVER['HTTP_X_REAL_HOST']; - } else { - $host = $this->server('HTTP_HOST'); + if (!$this->host) { + $this->host = $this->server('HTTP_X_REAL_HOST') ?: $this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'); } - return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + return true === $strict && strpos($this->host, ':') ? strstr($this->host, ':', true) : $this->host; } /** @@ -1402,7 +1819,7 @@ class Request /** * 当前请求 SERVER_PROTOCOL * @access public - * @return integer + * @return string */ public function protocol() { @@ -1427,6 +1844,7 @@ class Request public function contentType() { $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { if (strpos($contentType, ';')) { list($type) = explode(';', $contentType); @@ -1435,101 +1853,144 @@ class Request } return trim($type); } + return ''; } /** * 获取当前请求的路由信息 * @access public - * @param array $route 路由名称 + * @param array $route 路由名称 * @return array */ - public function routeInfo($route = []) + public function routeInfo(array $route = []) { if (!empty($route)) { $this->routeInfo = $route; - } else { - return $this->routeInfo; } + + return $this->routeInfo; } /** * 设置或者获取当前请求的调度信息 * @access public - * @param array $dispatch 调度信息 - * @return array + * @param \think\route\Dispatch $dispatch 调度信息 + * @return \think\route\Dispatch */ public function dispatch($dispatch = null) { if (!is_null($dispatch)) { $this->dispatch = $dispatch; } + return $this->dispatch; } /** - * 设置或者获取当前的模块名 + * 获取当前请求的安全Key * @access public - * @param string $module 模块名 - * @return string|Request + * @return string */ - public function module($module = null) + public function secureKey() { - if (!is_null($module)) { - $this->module = $module; - return $this; - } else { - return $this->module ?: ''; + if (is_null($this->secureKey)) { + $this->secureKey = uniqid('', true); } + + return $this->secureKey; } /** - * 设置或者获取当前的控制器名 + * 设置当前的模块名 * @access public - * @param string $controller 控制器名 - * @return string|Request + * @param string $module 模块名 + * @return $this */ - public function controller($controller = null) + public function setModule($module) { - if (!is_null($controller)) { - $this->controller = $controller; - return $this; - } else { - return $this->controller ?: ''; - } + $this->module = $module; + return $this; } /** - * 设置或者获取当前的操作名 + * 设置当前的控制器名 * @access public - * @param string $action 操作名 - * @return string|Request + * @param string $controller 控制器名 + * @return $this */ - public function action($action = null) + public function setController($controller) { - if (!is_null($action) && !is_bool($action)) { - $this->action = $action; - return $this; - } else { - $name = $this->action ?: ''; - return true === $action ? $name : strtolower($name); - } + $this->controller = $controller; + return $this; } /** - * 设置或者获取当前的语言 + * 设置当前的操作名 * @access public - * @param string $lang 语言名 - * @return string|Request + * @param string $action 操作名 + * @return $this */ - public function langset($lang = null) + public function setAction($action) { - if (!is_null($lang)) { - $this->langset = $lang; - return $this; - } else { - return $this->langset ?: ''; - } + $this->action = $action; + return $this; + } + + /** + * 获取当前的模块名 + * @access public + * @return string + */ + public function module() + { + return $this->module ?: ''; + } + + /** + * 获取当前的控制器名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function controller($convert = false) + { + $name = $this->controller ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 获取当前的操作名 + * @access public + * @param bool $convert 转换为驼峰 + * @return string + */ + public function action($convert = false) + { + $name = $this->action ?: ''; + return $convert ? $name : strtolower($name); + } + + /** + * 设置当前的语言 + * @access public + * @param string $lang 语言名 + * @return $this + */ + public function setLangset($lang) + { + $this->langset = $lang; + return $this; + } + + /** + * 获取当前的语言 + * @access public + * @return string + */ + public function langset() + { + return $this->langset ?: ''; } /** @@ -1542,6 +2003,7 @@ class Request if (is_null($this->content)) { $this->content = $this->input; } + return $this->content; } @@ -1558,29 +2020,32 @@ class Request /** * 生成请求令牌 * @access public - * @param string $name 令牌名称 - * @param mixed $type 令牌生成方法 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 * @return string */ - public function token($name = '__token__', $type = 'md5') + public function token($name = '__token__', $type = null) { $type = is_callable($type) ? $type : 'md5'; - $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); + if ($this->isAjax()) { header($name . ': ' . $token); } - Session::set($name, $token); + + facade\Session::set($name, $token); + return $token; } /** * 设置当前地址的请求缓存 * @access public - * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id - * @param mixed $expire 缓存有效期 - * @param array $except 缓存排除 - * @param string $tag 缓存标签 - * @return void + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return mixed */ public function cache($key, $expire = null, $except = [], $tag = null) { @@ -1589,62 +2054,56 @@ class Request $except = []; } - if (false !== $key && $this->isGet() && !$this->isCheckCache) { - // 标记请求缓存检查 - $this->isCheckCache = true; - if (false === $expire) { - // 关闭当前缓存 + if (false === $key || !$this->isGet() || $this->isCheckCache || false === $expire) { + // 关闭当前缓存 + return; + } + + // 标记请求缓存检查 + $this->isCheckCache = true; + + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { return; } - if ($key instanceof \Closure) { - $key = call_user_func_array($key, [$this]); - } elseif (true === $key) { - foreach ($except as $rule) { - if (0 === stripos($this->url(), $rule)) { - return; - } - } - // 自动缓存功能 - $key = '__URL__'; - } elseif (strpos($key, '|')) { - list($key, $fun) = explode('|', $key); - } - // 特殊规则替换 - if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); - } + } - if (false !== strpos($key, ':')) { - $param = $this->param(); - foreach ($param as $item => $val) { - if (is_string($val) && false !== strpos($key, ':' . $item)) { - $key = str_replace(':' . $item, $val, $key); - } - } - } elseif (strpos($key, ']')) { - if ('[' . $this->ext() . ']' == $key) { - // 缓存某个后缀的请求 - $key = md5($this->url()); - } else { - return; - } - } - if (isset($fun)) { - $key = $fun($key); - } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } - if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { - // 读取缓存 - $response = Response::create()->code(304); - throw new \think\exception\HttpResponseException($response); - } elseif (Cache::has($key)) { - list($content, $header) = Cache::get($key); - $response = Response::create($content)->header($header); - throw new \think\exception\HttpResponseException($response); + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); } else { - $this->cache = [$key, $expire, $tag]; + return; } } + + if (isset($fun)) { + $key = $fun($key); + } + + $this->cache = [$key, $expire, $tag]; + return $this->cache; } /** @@ -1658,33 +2117,151 @@ class Request } /** - * 设置当前请求绑定的对象实例 + * 设置GET数据 * @access public - * @param string|array $name 绑定的对象标识 - * @param mixed $obj 绑定的对象实例 - * @return mixed + * @param array $get 数据 + * @return $this */ - public function bind($name, $obj = null) + public function withGet(array $get) { - if (is_array($name)) { - $this->bind = array_merge($this->bind, $name); - } else { - $this->bind[$name] = $obj; - } + $this->get = $get; + return $this; } + /** + * 设置POST数据 + * @access public + * @param array $post 数据 + * @return $this + */ + public function withPost(array $post) + { + $this->post = $post; + return $this; + } + + /** + * 设置php://input数据 + * @access public + * @param string $input RAW数据 + * @return $this + */ + public function withInput($input) + { + $this->input = $input; + return $this; + } + + /** + * 设置文件上传数据 + * @access public + * @param array $files 上传信息 + * @return $this + */ + public function withFiles(array $files) + { + $this->file = $files; + return $this; + } + + /** + * 设置COOKIE数据 + * @access public + * @param array $cookie 数据 + * @return $this + */ + public function withCookie(array $cookie) + { + $this->cookie = $cookie; + return $this; + } + + /** + * 设置SERVER数据 + * @access public + * @param array $server 数据 + * @return $this + */ + public function withServer(array $server) + { + $this->server = array_change_key_case($server, CASE_UPPER); + return $this; + } + + /** + * 设置HEADER数据 + * @access public + * @param array $header 数据 + * @return $this + */ + public function withHeader(array $header) + { + $this->header = array_change_key_case($header); + return $this; + } + + /** + * 设置ENV数据 + * @access public + * @param array $env 数据 + * @return $this + */ + public function withEnv(array $env) + { + $this->env = $env; + return $this; + } + + /** + * 设置ROUTE变量 + * @access public + * @param array $route 数据 + * @return $this + */ + public function withRoute(array $route) + { + $this->route = $route; + return $this; + } + + /** + * 设置请求数据 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ public function __set($name, $value) { - $this->bind[$name] = $value; + return $this->param[$name] = $value; } + /** + * 获取请求数据的值 + * @access public + * @param string $name 参数名 + * @return mixed + */ public function __get($name) { - return isset($this->bind[$name]) ? $this->bind[$name] : null; + return $this->param($name); } + /** + * 检测请求数据的值 + * @access public + * @param string $name 名称 + * @return boolean + */ public function __isset($name) { - return isset($this->bind[$name]); + return isset($this->param[$name]); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['dispatch'], $data['config']); + + return $data; } } diff --git a/Server/thinkphp/library/think/Response.php b/Server/thinkphp/library/think/Response.php index c5c15209..5fa5402a 100644 --- a/Server/thinkphp/library/think/Response.php +++ b/Server/thinkphp/library/think/Response.php @@ -11,100 +11,135 @@ namespace think; -use think\response\Json as JsonResponse; -use think\response\Jsonp as JsonpResponse; use think\response\Redirect as RedirectResponse; -use think\response\View as ViewResponse; -use think\response\Xml as XmlResponse; class Response { - // 原始数据 + /** + * 原始数据 + * @var mixed + */ protected $data; - // 当前的contentType + /** + * 应用对象实例 + * @var App + */ + protected $app; + + /** + * 当前contentType + * @var string + */ protected $contentType = 'text/html'; - // 字符集 + /** + * 字符集 + * @var string + */ protected $charset = 'utf-8'; - //状态 + /** + * 状态码 + * @var integer + */ protected $code = 200; - // 输出参数 + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + + /** + * 输出参数 + * @var array + */ protected $options = []; - // header参数 + + /** + * header参数 + * @var array + */ protected $header = []; + /** + * 输出内容 + * @var string + */ protected $content = null; /** - * 构造函数 - * @access public - * @param mixed $data 输出数据 - * @param int $code - * @param array $header - * @param array $options 输出参数 + * 架构函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 */ public function __construct($data = '', $code = 200, array $header = [], $options = []) { $this->data($data); + if (!empty($options)) { $this->options = array_merge($this->options, $options); } + $this->contentType($this->contentType, $this->charset); - $this->header = array_merge($this->header, $header); + $this->code = $code; + $this->app = Container::get('app'); + $this->header = array_merge($this->header, $header); } /** * 创建Response对象 * @access public - * @param mixed $data 输出数据 - * @param string $type 输出类型 - * @param int $code - * @param array $header - * @param array $options 输出参数 - * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response */ public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) { $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + if (class_exists($class)) { - $response = new $class($data, $code, $header, $options); - } else { - $response = new static($data, $code, $header, $options); + return new $class($data, $code, $header, $options); } - return $response; + return new static($data, $code, $header, $options); } /** * 发送数据到客户端 * @access public - * @return mixed + * @return void * @throws \InvalidArgumentException */ public function send() { // 监听response_send - Hook::listen('response_send', $this); + $this->app['hook']->listen('response_send', $this); // 处理输出数据 $data = $this->getContent(); // Trace调试注入 - if (Env::get('app_trace', Config::get('app_trace'))) { - Debug::inject($this, $data); + if ('cli' != PHP_SAPI && $this->app['env']->get('app_trace', $this->app->config('app.app_trace'))) { + $this->app['debug']->inject($this, $data); } - if (200 == $this->code) { - $cache = Request::instance()->getCache(); + if (200 == $this->code && $this->allowCache) { + $cache = $this->app['request']->getCache(); if ($cache) { $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; - Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + + $this->app['cache']->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); } } @@ -113,15 +148,11 @@ class Response http_response_code($this->code); // 发送头部信息 foreach ($this->header as $name => $val) { - if (is_null($val)) { - header($name); - } else { - header($name . ':' . $val); - } + header($name . (!is_null($val) ? ':' . $val : '')); } } - echo $data; + $this->sendData($data); if (function_exists('fastcgi_finish_request')) { // 提高页面响应 @@ -129,18 +160,18 @@ class Response } // 监听response_end - Hook::listen('response_end', $this); + $this->app['hook']->listen('response_end', $this); // 清空当次请求有效的数据 if (!($this instanceof RedirectResponse)) { - Session::flush(); + $this->app['session']->flush(); } } /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) @@ -148,35 +179,61 @@ class Response return $data; } + /** + * 输出数据 + * @access protected + * @param string $data 要处理的数据 + * @return void + */ + protected function sendData($data) + { + echo $data; + } + /** * 输出的参数 * @access public - * @param mixed $options 输出参数 + * @param mixed $options 输出参数 * @return $this */ public function options($options = []) { $this->options = array_merge($this->options, $options); + return $this; } /** * 输出数据设置 * @access public - * @param mixed $data 输出数据 + * @param mixed $data 输出数据 * @return $this */ public function data($data) { $this->data = $data; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @param bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache($cache) + { + $this->allowCache = $cache; + return $this; } /** * 设置响应头 * @access public - * @param string|array $name 参数名 - * @param string $value 参数值 + * @param string|array $name 参数名 + * @param string $value 参数值 * @return $this */ public function header($name, $value = null) @@ -186,12 +243,14 @@ class Response } else { $this->header[$name] = $value; } + return $this; } /** * 设置页面输出内容 - * @param $content + * @access public + * @param mixed $content * @return $this */ public function content($content) @@ -211,87 +270,114 @@ class Response /** * 发送HTTP状态 - * @param integer $code 状态码 + * @access public + * @param integer $code 状态码 * @return $this */ public function code($code) { $this->code = $code; + return $this; } /** * LastModified - * @param string $time + * @access public + * @param string $time * @return $this */ public function lastModified($time) { $this->header['Last-Modified'] = $time; + return $this; } /** * Expires - * @param string $time + * @access public + * @param string $time * @return $this */ public function expires($time) { $this->header['Expires'] = $time; + return $this; } /** * ETag - * @param string $eTag + * @access public + * @param string $eTag * @return $this */ public function eTag($eTag) { $this->header['ETag'] = $eTag; + return $this; } /** * 页面缓存控制 - * @param string $cache 状态码 + * @access public + * @param string $cache 缓存设置 * @return $this */ public function cacheControl($cache) { $this->header['Cache-control'] = $cache; + + return $this; + } + + /** + * 设置页面不做任何缓存 + * @access public + * @return $this + */ + public function noCache() + { + $this->header['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'; + $this->header['Pragma'] = 'no-cache'; + return $this; } /** * 页面输出类型 - * @param string $contentType 输出类型 - * @param string $charset 输出编码 + * @access public + * @param string $contentType 输出类型 + * @param string $charset 输出编码 * @return $this */ public function contentType($contentType, $charset = 'utf-8') { $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + return $this; } /** * 获取头部信息 - * @param string $name 头部名称 + * @access public + * @param string $name 头部名称 * @return mixed */ public function getHeader($name = '') { if (!empty($name)) { return isset($this->header[$name]) ? $this->header[$name] : null; - } else { - return $this->header; } + + return $this->header; } /** * 获取原始数据 + * @access public * @return mixed */ public function getData() @@ -301,6 +387,7 @@ class Response /** * 获取输出数据 + * @access public * @return mixed */ public function getContent() @@ -318,15 +405,25 @@ class Response $this->content = (string) $content; } + return $this->content; } /** * 获取状态码 + * @access public * @return integer */ public function getCode() { return $this->code; } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } } diff --git a/Server/thinkphp/library/think/Route.php b/Server/thinkphp/library/think/Route.php index ab53aa20..97f6dc7d 100644 --- a/Server/thinkphp/library/think/Route.php +++ b/Server/thinkphp/library/think/Route.php @@ -11,39 +11,37 @@ namespace think; -use think\exception\HttpException; +use think\exception\RouteNotFoundException; +use think\route\AliasRule; +use think\route\Dispatch; +use think\route\dispatch\Url as UrlDispatch; +use think\route\Domain; +use think\route\Resource; +use think\route\Rule; +use think\route\RuleGroup; +use think\route\RuleItem; class Route { - // 路由规则 - private static $rules = [ - 'get' => [], - 'post' => [], - 'put' => [], - 'delete' => [], - 'patch' => [], - 'head' => [], - 'options' => [], - '*' => [], - 'alias' => [], - 'domain' => [], - 'pattern' => [], - 'name' => [], - ]; - - // REST路由操作方法定义 - private static $rest = [ + /** + * REST定义 + * @var array + */ + protected $rest = [ 'index' => ['get', '', 'index'], 'create' => ['get', '/create', 'create'], - 'edit' => ['get', '/:id/edit', 'edit'], - 'read' => ['get', '/:id', 'read'], + 'edit' => ['get', '//edit', 'edit'], + 'read' => ['get', '/', 'read'], 'save' => ['post', '', 'save'], - 'update' => ['put', '/:id', 'update'], - 'delete' => ['delete', '/:id', 'delete'], + 'update' => ['put', '/', 'update'], + 'delete' => ['delete', '/', 'delete'], ]; - // 不同请求类型的方法前缀 - private static $methodPrefix = [ + /** + * 请求方法前缀定义 + * @var array + */ + protected $methodPrefix = [ 'get' => 'get', 'post' => 'post', 'put' => 'put', @@ -51,170 +49,483 @@ class Route 'patch' => 'patch', ]; - // 子域名 - private static $subDomain = ''; - // 域名绑定 - private static $bind = []; - // 当前分组信息 - private static $group = []; - // 当前子域名绑定 - private static $domainBind; - private static $domainRule; - // 当前域名 - private static $domain; - // 当前路由执行过程中的参数 - private static $option = []; + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 当前HOST + * @var string + */ + protected $host; + + /** + * 当前域名 + * @var string + */ + protected $domain; + + /** + * 当前分组对象 + * @var RuleGroup + */ + protected $group; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 路由绑定 + * @var array + */ + protected $bind = []; + + /** + * 域名对象 + * @var array + */ + protected $domains = []; + + /** + * 跨域路由规则 + * @var RuleGroup + */ + protected $cross; + + /** + * 路由别名 + * @var array + */ + protected $alias = []; + + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = true; + + /** + * 路由是否测试模式 + * @var bool + */ + protected $isTest; + + /** + * (分组)路由规则是否合并解析 + * @var bool + */ + protected $mergeRuleRegex = true; + + /** + * 路由解析自动搜索多级控制器 + * @var bool + */ + protected $autoSearchController = true; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->request = $app['request']; + $this->config = $config; + + $this->host = $this->request->host(true) ?: $config['app_host']; + + $this->setDefaultDomain(); + } + + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + + return isset($this->config[$name]) ? $this->config[$name] : null; + } + + /** + * 配置 + * @access public + * @param array $config + * @return void + */ + public function setConfig(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(App $app, Config $config) + { + $config = $config->pull('app'); + $route = new static($app, $config); + + $route->lazy($config['url_lazy_route']) + ->autoSearchController($config['controller_auto_search']) + ->mergeRuleRegex($config['route_rule_merge']); + + return $route; + } + + /** + * 设置路由的请求对象实例 + * @access public + * @param Request $request 请求对象实例 + * @return void + */ + public function setRequest($request) + { + $this->request = $request; + } + + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + $this->lazy = $lazy; + return $this; + } + + /** + * 设置路由为测试模式 + * @access public + * @param bool $test 路由是否测试模式 + * @return void + */ + public function setTestMode($test) + { + $this->isTest = $test; + } + + /** + * 检查路由是否为测试模式 + * @access public + * @return bool + */ + public function isTest() + { + return $this->isTest; + } + + /** + * 设置路由域名及分组(包括资源路由)是否合并解析 + * @access public + * @param bool $merge 路由是否合并解析 + * @return $this + */ + public function mergeRuleRegex($merge = true) + { + $this->mergeRuleRegex = $merge; + $this->group->mergeRuleRegex($merge); + + return $this; + } + + /** + * 设置路由自动解析是否搜索多级控制器 + * @access public + * @param bool $auto 是否自动搜索多级控制器 + * @return $this + */ + public function autoSearchController($auto = true) + { + $this->autoSearchController = $auto; + return $this; + } + + /** + * 初始化默认域名 + * @access protected + * @return void + */ + protected function setDefaultDomain() + { + // 默认域名 + $this->domain = $this->host; + + // 注册默认域名 + $domain = new Domain($this, $this->host); + + $this->domains[$this->host] = $domain; + + // 默认分组 + $this->group = $domain; + } + + /** + * 设置当前域名 + * @access public + * @param RuleGroup $group 域名 + * @return void + */ + public function setGroup(RuleGroup $group) + { + $this->group = $group; + } + + /** + * 获取当前分组 + * @access public + * @return RuleGroup + */ + public function getGroup() + { + return $this->group; + } /** * 注册变量规则 * @access public - * @param string|array $name 变量名 - * @param string $rule 变量规则 - * @return void + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this */ - public static function pattern($name = null, $rule = '') + public function pattern($name, $rule = '') { - if (is_array($name)) { - self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); - } else { - self::$rules['pattern'][$name] = $rule; - } + $this->group->pattern($name, $rule); + + return $this; } /** - * 注册子域名部署规则 + * 注册路由参数 * @access public - * @param string|array $domain 子域名 - * @param mixed $rule 路由规则 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this */ - public static function domain($domain, $rule = '', $option = [], $pattern = []) + public function option($name, $value = '') { - if (is_array($domain)) { - foreach ($domain as $key => $item) { - self::domain($key, $item, $option, $pattern); - } - } elseif ($rule instanceof \Closure) { - // 执行闭包 - self::setDomain($domain); - call_user_func_array($rule, []); - self::setDomain(null); - } elseif (is_array($rule)) { - self::setDomain($domain); - self::group('', function () use ($rule) { - // 动态注册域名的路由规则 - self::registerRules($rule); - }, $option, $pattern); - self::setDomain(null); - } else { - self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; - } + $this->group->option($name, $value); + + return $this; } - private static function setDomain($domain) + /** + * 注册域名路由 + * @access public + * @param string|array $name 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Domain + */ + public function domain($name, $rule = '', $option = [], $pattern = []) { - self::$domain = $domain; + // 支持多个域名使用相同路由规则 + $domainName = is_array($name) ? array_shift($name) : $name; + + if ('*' != $domainName && false === strpos($domainName, '.')) { + $domainName .= '.' . $this->request->rootDomain(); + } + + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); + + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); + } + + if (is_array($name) && !empty($name)) { + $root = $this->request->rootDomain(); + foreach ($name as $item) { + if (false === strpos($item, '.')) { + $item .= '.' . $root; + } + + $this->domains[$item] = $domainName; + } + } + + // 返回域名对象 + return $domain; + } + + /** + * 获取域名 + * @access public + * @return array + */ + public function getDomains() + { + return $this->domains; } /** * 设置路由绑定 * @access public - * @param mixed $bind 绑定信息 - * @param string $type 绑定类型 默认为module 支持 namespace class controller - * @return mixed + * @param string $bind 绑定信息 + * @param string $domain 域名 + * @return $this */ - public static function bind($bind, $type = 'module') + public function bind($bind, $domain = null) { - self::$bind = ['type' => $type, $type => $bind]; - } + $domain = is_null($domain) ? $this->domain : $domain; - /** - * 设置或者获取路由标识 - * @access public - * @param string|array $name 路由命名标识 数组表示批量设置 - * @param array $value 路由地址及变量信息 - * @return array - */ - public static function name($name = '', $value = null) - { - if (is_array($name)) { - return self::$rules['name'] = $name; - } elseif ('' === $name) { - return self::$rules['name']; - } elseif (!is_null($value)) { - self::$rules['name'][strtolower($name)][] = $value; - } else { - $name = strtolower($name); - return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; - } + $this->bind[$domain] = $bind; + + return $this; } /** * 读取路由绑定 * @access public - * @param string $type 绑定类型 + * @param string $domain 域名 + * @return string|null + */ + public function getBind($domain = null) + { + if (is_null($domain)) { + $domain = $this->domain; + } elseif (true === $domain) { + return $this->bind; + } elseif (false === strpos($domain, '.')) { + $domain .= '.' . $this->request->rootDomain(); + } + + $subDomain = $this->request->subDomain(); + + if (strpos($subDomain, '.')) { + $name = '*' . strstr($subDomain, '.'); + } + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($name) && isset($this->bind[$name])) { + $result = $this->bind[$name]; + } elseif (!empty($subDomain) && isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; + } + + /** + * 读取路由标识 + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 * @return mixed */ - public static function getBind($type) + public function getName($name = null, $domain = null, $method = '*') { - return isset(self::$bind[$type]) ? self::$bind[$type] : null; + return $this->app['rule_name']->get($name, $domain, $method); + } + + /** + * 读取路由 + * @access public + * @param string $rule 路由规则 + * @param string $domain 域名 + * @return array + */ + public function getRule($rule, $domain = null) + { + if (is_null($domain)) { + $domain = $this->domain; + } + + return $this->app['rule_name']->getRule($rule, $domain); + } + + /** + * 读取路由 + * @access public + * @param string $domain 域名 + * @return array + */ + public function getRuleList($domain = null) + { + return $this->app['rule_name']->getRuleList($domain); + } + + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return $this + */ + public function setName($name) + { + $this->app['rule_name']->import($name); + return $this; } /** * 导入配置文件的路由规则 * @access public - * @param array $rule 路由规则 - * @param string $type 请求类型 + * @param array $rules 路由规则 + * @param string $type 请求类型 * @return void */ - public static function import(array $rule, $type = '*') + public function import(array $rules, $type = '*') { // 检查域名部署 - if (isset($rule['__domain__'])) { - self::domain($rule['__domain__']); - unset($rule['__domain__']); + if (isset($rules['__domain__'])) { + foreach ($rules['__domain__'] as $key => $rule) { + $this->domain($key, $rule); + } + unset($rules['__domain__']); } // 检查变量规则 - if (isset($rule['__pattern__'])) { - self::pattern($rule['__pattern__']); - unset($rule['__pattern__']); + if (isset($rules['__pattern__'])) { + $this->pattern($rules['__pattern__']); + unset($rules['__pattern__']); } // 检查路由别名 - if (isset($rule['__alias__'])) { - self::alias($rule['__alias__']); - unset($rule['__alias__']); + if (isset($rules['__alias__'])) { + foreach ($rules['__alias__'] as $key => $val) { + $this->alias($key, $val); + } + unset($rules['__alias__']); } // 检查资源路由 - if (isset($rule['__rest__'])) { - self::resource($rule['__rest__']); - unset($rule['__rest__']); + if (isset($rules['__rest__'])) { + foreach ($rules['__rest__'] as $key => $rule) { + $this->resource($key, $rule); + } + unset($rules['__rest__']); } - self::registerRules($rule, strtolower($type)); - } - - // 批量注册路由 - protected static function registerRules($rules, $type = '*') - { + // 检查路由规则(包含分组) foreach ($rules as $key => $val) { if (is_numeric($key)) { $key = array_shift($val); } + if (empty($val)) { continue; } + if (is_string($key) && 0 === strpos($key, '[')) { $key = substr($key, 1, -1); - self::group($key, $val); + $this->group($key, $val); } elseif (is_array($val)) { - self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + $this->rule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); } else { - self::setRule($key, $val, $type); + $this->rule($key, $val, $type); } } } @@ -222,1424 +533,460 @@ class Route /** * 注册路由规则 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function rule($rule, $route, $method = '*', array $option = [], array $pattern = []) + { + return $this->group->addRule($rule, $route, $method, $option, $pattern); + } + + /** + * 设置跨域有效路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function setCrossDomainRule($rule, $method = '*') + { + if (!isset($this->cross)) { + $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex); + } + + $this->cross->addRuleItem($rule, $method); + + return $this; + } + + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 * @return void */ - public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) + public function rules($rules, $method = '*', array $option = [], array $pattern = []) { - $group = self::getGroup('name'); - - if (!is_null($group)) { - // 路由分组 - $option = array_merge(self::getGroup('option'), $option); - $pattern = array_merge(self::getGroup('pattern'), $pattern); - } - - $type = strtolower($type); - - if (strpos($type, '|')) { - $option['method'] = $type; - $type = '*'; - } - if (is_array($rule) && empty($route)) { - foreach ($rule as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, $val[1]); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $option1 = null; - $pattern1 = null; - $route = $val; - } - self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group); - } - } else { - self::setRule($rule, $route, $type, $option, $pattern, $group); - } - - } - - /** - * 设置路由规则 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @param string $group 所属分组 - * @return void - */ - protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') - { - if (is_array($rule)) { - $name = $rule[0]; - $rule = $rule[1]; - } elseif (is_string($route)) { - $name = $route; - } - if (!isset($option['complete_match'])) { - if (Config::get('route_complete_match')) { - $option['complete_match'] = true; - } elseif ('$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - - if ('$' == substr($rule, -1, 1)) { - $rule = substr($rule, 0, -1); - } - - if ('/' != $rule || $group) { - $rule = trim($rule, '/'); - } - $vars = self::parseVar($rule); - if (isset($name)) { - $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; - $suffix = isset($option['ext']) ? $option['ext'] : null; - self::name($name, [$key, $vars, self::$domain, $suffix]); - } - if (isset($option['modular'])) { - $route = $option['modular'] . '/' . $route; - } - if ($group) { - if ('*' != $type) { - $option['method'] = $type; - } - if (self::$domain) { - self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - } else { - if ('*' != $type && isset(self::$rules['*'][$rule])) { - unset(self::$rules['*'][$rule]); - } - if (self::$domain) { - self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - if ('*' == $type) { - // 注册路由快捷方式 - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) { - self::$rules['domain'][self::$domain][$method][$rule] = true; - } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) { - self::$rules[$method][$rule] = true; - } - } - } - } - } - - /** - * 设置当前执行的参数信息 - * @access public - * @param array $options 参数信息 - * @return mixed - */ - protected static function setOption($options = []) - { - self::$option[] = $options; - } - - /** - * 获取当前执行的所有参数信息 - * @access public - * @return array - */ - public static function getOption() - { - return self::$option; - } - - /** - * 获取当前的分组信息 - * @access public - * @param string $type 分组信息名称 name option pattern - * @return mixed - */ - public static function getGroup($type) - { - if (isset(self::$group[$type])) { - return self::$group[$type]; - } else { - return 'name' == $type ? null : []; - } - } - - /** - * 设置当前的路由分组 - * @access public - * @param string $name 分组名称 - * @param array $option 分组路由参数 - * @param array $pattern 分组变量规则 - * @return void - */ - public static function setGroup($name, $option = [], $pattern = []) - { - self::$group['name'] = $name; - self::$group['option'] = $option ?: []; - self::$group['pattern'] = $pattern ?: []; + $this->group->addRules($rules, $method, $option, $pattern); } /** * 注册路由分组 * @access public - * @param string|array $name 分组名称或者参数 - * @param array|\Closure $routes 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $route 分组路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleGroup */ - public static function group($name, $routes, $option = [], $pattern = []) + public function group($name, $route, array $option = [], array $pattern = []) { if (is_array($name)) { $option = $name; $name = isset($option['name']) ? $option['name'] : ''; } - // 分组 - $currentGroup = self::getGroup('name'); - if ($currentGroup) { - $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); - } - if (!empty($name)) { - if ($routes instanceof \Closure) { - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - if ($currentGroup != $name) { - self::$rules['*'][$name]['route'] = ''; - self::$rules['*'][$name]['var'] = self::parseVar($name); - self::$rules['*'][$name]['option'] = $option; - self::$rules['*'][$name]['pattern'] = $pattern; - } - } else { - $item = []; - $completeMatch = Config::get('route_complete_match'); - foreach ($routes as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $route = $val; - } - $options = isset($option1) ? $option1 : $option; - $patterns = isset($pattern1) ? $pattern1 : $pattern; - if ('$' == substr($key, -1, 1)) { - // 是否完整匹配 - $options['complete_match'] = true; - $key = substr($key, 0, -1); - } elseif ($completeMatch) { - $options['complete_match'] = true; - } - $key = trim($key, '/'); - $vars = self::parseVar($key); - $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; - // 设置路由标识 - $suffix = isset($options['ext']) ? $options['ext'] : null; - self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); - } - self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; - } - - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (!isset(self::$rules[$method][$name])) { - self::$rules[$method][$name] = true; - } elseif (is_array(self::$rules[$method][$name])) { - self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); - } - } - - } elseif ($routes instanceof \Closure) { - // 闭包注册 - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - } else { - // 批量注册路由 - self::rule($routes, '', '*', $option, $pattern); - } + return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); } /** * 注册路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function any($rule, $route = '', $option = [], $pattern = []) + public function any($rule, $route = '', array $option = [], array $pattern = []) { - self::rule($rule, $route, '*', $option, $pattern); + return $this->rule($rule, $route, '*', $option, $pattern); } /** * 注册GET路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function get($rule, $route = '', $option = [], $pattern = []) + public function get($rule, $route = '', array $option = [], array $pattern = []) { - self::rule($rule, $route, 'GET', $option, $pattern); + return $this->rule($rule, $route, 'GET', $option, $pattern); } /** * 注册POST路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function post($rule, $route = '', $option = [], $pattern = []) + public function post($rule, $route = '', array $option = [], array $pattern = []) { - self::rule($rule, $route, 'POST', $option, $pattern); + return $this->rule($rule, $route, 'POST', $option, $pattern); } /** * 注册PUT路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function put($rule, $route = '', $option = [], $pattern = []) + public function put($rule, $route = '', array $option = [], array $pattern = []) { - self::rule($rule, $route, 'PUT', $option, $pattern); + return $this->rule($rule, $route, 'PUT', $option, $pattern); } /** * 注册DELETE路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function delete($rule, $route = '', $option = [], $pattern = []) + public function delete($rule, $route = '', array $option = [], array $pattern = []) { - self::rule($rule, $route, 'DELETE', $option, $pattern); + return $this->rule($rule, $route, 'DELETE', $option, $pattern); } /** * 注册PATCH路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem */ - public static function patch($rule, $route = '', $option = [], $pattern = []) + public function patch($rule, $route = '', array $option = [], array $pattern = []) { - self::rule($rule, $route, 'PATCH', $option, $pattern); + return $this->rule($rule, $route, 'PATCH', $option, $pattern); } /** * 注册资源路由 * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Resource */ - public static function resource($rule, $route = '', $option = [], $pattern = []) + public function resource($rule, $route = '', array $option = [], array $pattern = []) { - if (is_array($rule)) { - foreach ($rule as $key => $val) { - if (is_array($val)) { - list($val, $option, $pattern) = array_pad($val, 3, []); - } - self::resource($key, $val, $option, $pattern); - } - } else { - if (strpos($rule, '.')) { - // 注册嵌套资源路由 - $array = explode('.', $rule); - $last = array_pop($array); - $item = []; - foreach ($array as $val) { - $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); - } - $rule = implode('/', $item) . '/' . $last; - } - // 注册资源路由 - foreach (self::$rest as $key => $val) { - if ((isset($option['only']) && !in_array($key, $option['only'])) - || (isset($option['except']) && in_array($key, $option['except']))) { - continue; - } - if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { - $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); - } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { - $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); - } - $item = ltrim($rule . $val[1], '/'); - $option['rest'] = $key; - self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); - } - } + return (new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest)) + ->lazy($this->lazy); } /** - * 注册控制器路由 操作方法对应不同的请求后缀 + * 注册控制器路由 操作方法对应不同的请求前缀 * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleGroup */ - public static function controller($rule, $route = '', $option = [], $pattern = []) + public function controller($rule, $route = '', array $option = [], array $pattern = []) { - foreach (self::$methodPrefix as $type => $val) { - self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern); + + foreach ($this->methodPrefix as $type => $val) { + $group->addRule('', $val . '', $type); } + + return $group->prefix($route . '/'); + } + + /** + * 注册视图路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function view($rule, $template = '', array $vars = [], array $option = [], array $pattern = []) + { + return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars); + } + + /** + * 注册重定向路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $status 状态码 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function redirect($rule, $route = '', $status = 301, array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status); } /** * 注册别名路由 * @access public - * @param string|array $rule 路由别名 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @return void + * @param string $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return AliasRule */ - public static function alias($rule = null, $route = '', $option = []) + public function alias($rule, $route, array $option = []) { - if (is_array($rule)) { - self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); - } else { - self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; + $aliasRule = new AliasRule($this, $this->group, $rule, $route, $option); + + $this->alias[$rule] = $aliasRule; + + return $aliasRule; + } + + /** + * 获取别名路由定义 + * @access public + * @param string $name 路由别名 + * @return string|array|null + */ + public function getAlias($name = null) + { + if (is_null($name)) { + return $this->alias; } + + return isset($this->alias[$name]) ? $this->alias[$name] : null; } /** * 设置不同请求类型下面的方法前缀 * @access public - * @param string $method 请求类型 - * @param string $prefix 类型前缀 - * @return void + * @param string|array $method 请求类型 + * @param string $prefix 类型前缀 + * @return $this */ - public static function setMethodPrefix($method, $prefix = '') + public function setMethodPrefix($method, $prefix = '') { if (is_array($method)) { - self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); + $this->methodPrefix = array_merge($this->methodPrefix, array_change_key_case($method)); } else { - self::$methodPrefix[strtolower($method)] = $prefix; + $this->methodPrefix[strtolower($method)] = $prefix; } + + return $this; + } + + /** + * 获取请求类型的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return string|null + */ + public function getMethodPrefix($method) + { + $method = strtolower($method); + + return isset($this->methodPrefix[$method]) ? $this->methodPrefix[$method] : null; } /** * rest方法定义和修改 * @access public - * @param string|array $name 方法名称 - * @param array|bool $resource 资源 - * @return void + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this */ - public static function rest($name, $resource = []) + public function rest($name, $resource = []) { if (is_array($name)) { - self::$rest = $resource ? $name : array_merge(self::$rest, $name); + $this->rest = $resource ? $name : array_merge($this->rest, $name); } else { - self::$rest[$name] = $resource; + $this->rest[$name] = $resource; } + + return $this; + } + + /** + * 获取rest方法定义的参数 + * @access public + * @param string $name 方法名称 + * @return array|null + */ + public function getRest($name = null) + { + if (is_null($name)) { + return $this->rest; + } + + return isset($this->rest[$name]) ? $this->rest[$name] : null; } /** * 注册未匹配路由规则后的处理 * @access public - * @param string $route 路由地址 - * @param string $method 请求类型 - * @param array $option 路由参数 - * @return void + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem */ - public static function miss($route, $method = '*', $option = []) + public function miss($route, $method = '*', array $option = []) { - self::rule('__miss__', $route, $method, $option, []); + return $this->group->addMissRule($route, $method, $option); } /** * 注册一个自动解析的URL路由 * @access public - * @param string $route 路由地址 - * @return void + * @param string $route 路由地址 + * @return RuleItem */ - public static function auto($route) + public function auto($route) { - self::rule('__auto__', $route, '*', [], []); - } - - /** - * 获取或者批量设置路由定义 - * @access public - * @param mixed $rules 请求类型或者路由定义数组 - * @return array - */ - public static function rules($rules = '') - { - if (is_array($rules)) { - self::$rules = $rules; - } elseif ($rules) { - return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; - } else { - $rules = self::$rules; - unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); - return $rules; - } - } - - /** - * 检测子域名部署 - * @access public - * @param Request $request Request请求对象 - * @param array $currentRules 当前路由规则 - * @param string $method 请求类型 - * @return void - */ - public static function checkDomain($request, &$currentRules, $method = 'get') - { - // 域名规则 - $rules = self::$rules['domain']; - // 开启子域名部署 支持二级和三级域名 - if (!empty($rules)) { - $host = $request->host(true); - if (isset($rules[$host])) { - // 完整域名或者IP配置 - $item = $rules[$host]; - } else { - $rootDomain = Config::get('url_domain_root'); - if ($rootDomain) { - // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 - $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); - } else { - $domain = explode('.', $host, -2); - } - // 子域名配置 - if (!empty($domain)) { - // 当前子域名 - $subDomain = implode('.', $domain); - self::$subDomain = $subDomain; - $domain2 = array_pop($domain); - if ($domain) { - // 存在三级域名 - $domain3 = array_pop($domain); - } - if ($subDomain && isset($rules[$subDomain])) { - // 子域名配置 - $item = $rules[$subDomain]; - } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { - // 泛三级域名 - $item = $rules['*.' . $domain2]; - $panDomain = $domain3; - } elseif (isset($rules['*']) && !empty($domain2)) { - // 泛二级域名 - if ('www' != $domain2) { - $item = $rules['*']; - $panDomain = $domain2; - } - } - } - } - if (!empty($item)) { - if (isset($panDomain)) { - // 保存当前泛域名 - $request->route(['__domain__' => $panDomain]); - } - if (isset($item['[bind]'])) { - // 解析子域名部署规则 - list($rule, $option, $pattern) = $item['[bind]']; - if (!empty($option['https']) && !$request->isSsl()) { - // https检测 - throw new HttpException(404, 'must use https request:' . $host); - } - - if (strpos($rule, '?')) { - // 传入其它参数 - $array = parse_url($rule); - $result = $array['path']; - parse_str($array['query'], $params); - if (isset($panDomain)) { - $pos = array_search('*', $params); - if (false !== $pos) { - // 泛域名作为参数 - $params[$pos] = $panDomain; - } - } - $_GET = array_merge($_GET, $params); - } else { - $result = $rule; - } - - if (0 === strpos($result, '\\')) { - // 绑定到命名空间 例如 \app\index\behavior - self::$bind = ['type' => 'namespace', 'namespace' => $result]; - } elseif (0 === strpos($result, '@')) { - // 绑定到类 例如 @app\index\controller\User - self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; - } else { - // 绑定到模块/控制器 例如 index/user - self::$bind = ['type' => 'module', 'module' => $result]; - } - self::$domainBind = true; - } else { - self::$domainRule = $item; - $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; - } - } - } + return $this->group->addAutoRule($route); } /** * 检测URL路由 * @access public - * @param Request $request Request请求对象 - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $checkDomain 是否检测域名规则 - * @return false|array + * @param string $url URL地址 + * @param bool $must 是否强制路由 + * @return Dispatch + * @throws RouteNotFoundException */ - public static function check($request, $url, $depr = '/', $checkDomain = false) + public function check($url, $must = false) { - //检查解析缓存 - if (!App::$debug && Config::get('route_check_cache')) { - $key = self::getCheckCacheKey($request); - if (Cache::has($key)) { - list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key); - return self::parseRule($rule, $route, $pathinfo, $option, $matches, true); - } + // 自动检测域名路由 + $domain = $this->checkDomain(); + $url = str_replace($this->config['pathinfo_depr'], '|', $url); + + $completeMatch = $this->config['route_complete_match']; + + $result = $domain->check($this->request, $url, $completeMatch); + + if (false === $result && !empty($this->cross)) { + // 检测跨域路由 + $result = $this->cross->check($this->request, $url, $completeMatch); } - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace($depr, '|', $url); - - if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { - // 检测路由别名 - $result = self::checkRouteAlias($request, $url, $depr); - if (false !== $result) { - return $result; - } - } - $method = strtolower($request->method()); - // 获取当前请求类型的路由规则 - $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; - // 检测域名部署 - if ($checkDomain) { - self::checkDomain($request, $rules, $method); - } - // 检测URL绑定 - $return = self::checkUrlBind($url, $rules, $depr); - if (false !== $return) { - return $return; - } - if ('|' != $url) { - $url = rtrim($url, '|'); - } - $item = str_replace('|', '/', $url); - if (isset($rules[$item])) { - // 静态路由规则检测 - $rule = $rules[$item]; - if (true === $rule) { - $rule = self::getRouteExpress($item); - } - if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { - self::setOption($rule['option']); - return self::parseRule($item, $rule['route'], $url, $rule['option']); - } + if (false !== $result) { + // 路由匹配 + return $result; + } elseif ($must) { + // 强制路由不匹配则抛出异常 + throw new RouteNotFoundException(); } - // 路由规则检测 - if (!empty($rules)) { - return self::checkRoute($request, $rules, $url, $depr); - } - return false; - } - - private static function getRouteExpress($key) - { - return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; + // 默认路由解析 + return new UrlDispatch($this->request, $this->group, $url, [ + 'auto_search' => $this->autoSearchController, + ]); } /** - * 检测路由规则 - * @access private - * @param Request $request - * @param array $rules 路由规则 - * @param string $url URL地址 - * @param string $depr URL分割符 - * @param string $group 路由分组名 - * @param array $options 路由参数(分组) - * @return mixed + * 检测域名的路由规则 + * @access protected + * @return Domain */ - private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) + protected function checkDomain() { - foreach ($rules as $key => $item) { - if (true === $item) { - $item = self::getRouteExpress($key); - } - if (!isset($item['rule'])) { - continue; - } - $rule = $item['rule']; - $route = $item['route']; - $vars = $item['var']; - $option = $item['option']; - $pattern = $item['pattern']; + // 获取当前子域名 + $subDomain = $this->request->subDomain(); - // 检查参数有效性 - if (!self::checkOption($option, $request)) { - continue; + $item = false; + + if ($subDomain && count($this->domains) > 1) { + $domain = explode('.', $subDomain); + $domain2 = array_pop($domain); + + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); } - if (isset($option['ext'])) { - // 路由ext参数 优先于系统配置的URL伪静态后缀参数 - $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); + if ($subDomain && isset($this->domains[$subDomain])) { + // 子域名配置 + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $this->domains['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($this->domains['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $this->domains['*']; + $panDomain = $domain2; + } } - if (is_array($rule)) { - // 分组路由 - $pos = strpos(str_replace('<', ':', $key), ':'); - if (false !== $pos) { - $str = substr($key, 0, $pos); - } else { - $str = $key; - } - if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { - continue; - } - self::setOption($option); - $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); - if (false !== $result) { - return $result; - } - } elseif ($route) { - if ('__miss__' == $rule || '__auto__' == $rule) { - // 指定特殊路由 - $var = trim($rule, '__'); - ${$var} = $item; - continue; - } - if ($group) { - $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); - } - - self::setOption($option); - if (isset($options['bind_model']) && isset($option['bind_model'])) { - $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); - } - $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); - if (false !== $result) { - return $result; - } + if (isset($panDomain)) { + // 保存当前泛域名 + $this->request->setPanDomain($panDomain); } } - if (isset($auto)) { - // 自动解析URL地址 - return self::parseUrl($auto['route'] . '/' . $url, $depr); - } elseif (isset($miss)) { - // 未匹配所有路由的路由规则处理 - return self::parseRule('', $miss['route'], $url, $miss['option']); + + if (false === $item) { + // 检测当前完整域名 + $item = $this->domains[$this->host]; } - return false; + + if (is_string($item)) { + $item = $this->domains[$item]; + } + + return $item; } /** - * 检测路由别名 - * @access private - * @param Request $request - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkRouteAlias($request, $url, $depr) - { - $array = explode('|', $url); - $alias = array_shift($array); - $item = self::$rules['alias'][$alias]; - - if (is_array($item)) { - list($rule, $option) = $item; - $action = $array[0]; - if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { - // 允许操作 - return false; - } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { - // 排除操作 - return false; - } - if (isset($option['method'][$action])) { - $option['method'] = $option['method'][$action]; - } - } else { - $rule = $item; - } - $bind = implode('|', $array); - // 参数有效性检查 - if (isset($option) && !self::checkOption($option, $request)) { - // 路由不匹配 - return false; - } elseif (0 === strpos($rule, '\\')) { - // 路由到类 - return self::bindToClass($bind, substr($rule, 1), $depr); - } elseif (0 === strpos($rule, '@')) { - // 路由到控制器类 - return self::bindToController($bind, substr($rule, 1), $depr); - } else { - // 路由到模块/控制器 - return self::bindToModule($bind, $rule, $depr); - } - } - - /** - * 检测URL绑定 - * @access private - * @param string $url URL地址 - * @param array $rules 路由规则 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkUrlBind(&$url, &$rules, $depr = '/') - { - if (!empty(self::$bind)) { - $type = self::$bind['type']; - $bind = self::$bind[$type]; - // 记录绑定信息 - App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); - // 如果有URL绑定 则进行绑定检测 - switch ($type) { - case 'class': - // 绑定到类 - return self::bindToClass($url, $bind, $depr); - case 'controller': - // 绑定到控制器类 - return self::bindToController($url, $bind, $depr); - case 'namespace': - // 绑定到命名空间 - return self::bindToNamespace($url, $bind, $depr); - } - } - return false; - } - - /** - * 绑定到类 + * 清空路由规则 * @access public - * @param string $url URL地址 - * @param string $class 类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToClass($url, $class, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; - } - - /** - * 绑定到命名空间 - * @access public - * @param string $url URL地址 - * @param string $namespace 命名空间 - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToNamespace($url, $namespace, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 3); - $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); - $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); - if (!empty($array[2])) { - self::parseUrlParams($array[2]); - } - return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; - } - - /** - * 绑定到控制器类 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器名 (支持带模块名 index/user ) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToController($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; - } - - /** - * 绑定到模块/控制器 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToModule($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'module', 'module' => $controller . '/' . $action]; - } - - /** - * 路由参数有效性检查 - * @access private - * @param array $option 路由参数 - * @param Request $request Request对象 - * @return bool - */ - private static function checkOption($option, $request) - { - if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) - || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 - || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 - || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 - || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 - || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 - || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) - || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 - || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 - || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 - || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 - || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 - ) { - return false; - } - return true; - } - - /** - * 检测路由规则 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $url URL地址 - * @param array $pattern 变量规则 - * @param array $option 路由参数 - * @param string $depr URL分隔符(全局) - * @return array|false - */ - private static function checkRule($rule, $route, $url, $pattern, $option, $depr) - { - // 检查完整规则定义 - if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { - return false; - } - // 检查路由的参数分隔符 - if (isset($option['param_depr'])) { - $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); - } - - $len1 = substr_count($url, '|'); - $len2 = substr_count($rule, '/'); - // 多余参数是否合并 - $merge = !empty($option['merge_extra_vars']); - if ($merge && $len1 > $len2) { - $url = str_replace('|', $depr, $url); - $url = implode('|', explode($depr, $url, $len2 + 1)); - } - - if ($len1 >= $len2 || strpos($rule, '[')) { - if (!empty($option['complete_match'])) { - // 完整匹配 - if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { - return false; - } - } - $pattern = array_merge(self::$rules['pattern'], $pattern); - if (false !== $match = self::match($url, $rule, $pattern)) { - // 匹配到路由规则 - return self::parseRule($rule, $route, $url, $option, $match); - } - } - return false; - } - - /** - * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... - * @access public - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $autoSearch 是否自动深度搜索控制器 - * @return array - */ - public static function parseUrl($url, $depr = '/', $autoSearch = false) - { - - if (isset(self::$bind['module'])) { - $bind = str_replace('/', $depr, self::$bind['module']); - // 如果有模块/控制器绑定 - $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); - } - $url = str_replace($depr, '|', $url); - list($path, $var) = self::parseUrlPath($url); - $route = [null, null, null]; - if (isset($path)) { - // 解析模块 - $module = Config::get('app_multi_module') ? array_shift($path) : null; - if ($autoSearch) { - // 自动搜索控制器 - $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); - $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; - $item = []; - $find = false; - foreach ($path as $val) { - $item[] = $val; - $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; - $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; - if (is_file($file)) { - $find = true; - break; - } else { - $dir .= DS . Loader::parseName($val); - } - } - if ($find) { - $controller = implode('.', $item); - $path = array_slice($path, count($item)); - } else { - $controller = array_shift($path); - } - } else { - // 解析控制器 - $controller = !empty($path) ? array_shift($path) : null; - } - // 解析操作 - $action = !empty($path) ? array_shift($path) : null; - // 解析额外参数 - self::parseUrlParams(empty($path) ? '' : implode('|', $path)); - // 封装路由 - $route = [$module, $controller, $action]; - // 检查地址是否被定义过路由 - $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); - $name2 = ''; - if (empty($module) || isset($bind) && $module == $bind) { - $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); - } - - if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { - throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); - } - } - return ['type' => 'module', 'module' => $route]; - } - - /** - * 解析URL的pathinfo参数和变量 - * @access private - * @param string $url URL地址 - * @return array - */ - private static function parseUrlPath($url) - { - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace('|', '/', $url); - $url = trim($url, '/'); - $var = []; - if (false !== strpos($url, '?')) { - // [模块/控制器/操作?]参数1=值1&参数2=值2... - $info = parse_url($url); - $path = explode('/', $info['path']); - parse_str($info['query'], $var); - } elseif (strpos($url, '/')) { - // [模块/控制器/操作] - $path = explode('/', $url); - } else { - $path = [$url]; - } - return [$path, $var]; - } - - /** - * 检测URL和规则路由是否匹配 - * @access private - * @param string $url URL地址 - * @param string $rule 路由规则 - * @param array $pattern 变量规则 - * @return array|false - */ - private static function match($url, $rule, $pattern) - { - $m2 = explode('/', $rule); - $m1 = explode('|', $url); - - $var = []; - foreach ($m2 as $key => $val) { - // val中定义了多个变量 - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - $value = []; - $replace = []; - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; - } else { - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; - } - $value[] = $name; - } - $val = str_replace($matches[0], $replace, $val); - if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { - array_shift($match); - foreach ($value as $k => $name) { - if (isset($match[$k])) { - $var[$name] = $match[$k]; - } - } - continue; - } else { - return false; - } - } - - if (0 === strpos($val, '[:')) { - // 可选参数 - $val = substr($val, 1, -1); - $optional = true; - } else { - $optional = false; - } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - if (!$optional && !isset($m1[$key])) { - return false; - } - if (isset($m1[$key]) && isset($pattern[$name])) { - // 检查变量规则 - if ($pattern[$name] instanceof \Closure) { - $result = call_user_func_array($pattern[$name], [$m1[$key]]); - if (false === $result) { - return false; - } - } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { - return false; - } - } - $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; - } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { - return false; - } - } - // 成功匹配后返回URL中的动态变量数组 - return $var; - } - - /** - * 解析规则路由 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $pathinfo URL地址 - * @param array $option 路由参数 - * @param array $matches 匹配的变量 - * @param bool $fromCache 通过缓存解析 - * @return array - */ - private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false) - { - $request = Request::instance(); - - //保存解析缓存 - if (Config::get('route_check_cache') && !$fromCache) { - try { - $key = self::getCheckCacheKey($request); - Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]); - } catch (\Exception $e) { - - } - } - - // 解析路由规则 - if ($rule) { - $rule = explode('/', $rule); - // 获取URL地址中的参数 - $paths = explode('|', $pathinfo); - foreach ($rule as $item) { - $fun = ''; - if (0 === strpos($item, '[:')) { - $item = substr($item, 1, -1); - } - if (0 === strpos($item, ':')) { - $var = substr($item, 1); - $matches[$var] = array_shift($paths); - } else { - // 过滤URL中的静态变量 - array_shift($paths); - } - } - } else { - $paths = explode('|', $pathinfo); - } - - // 获取路由地址规则 - if (is_string($route) && isset($option['prefix'])) { - // 路由地址前缀 - $route = $option['prefix'] . $route; - } - // 替换路由地址中的变量 - if (is_string($route) && !empty($matches)) { - foreach ($matches as $key => $val) { - if (false !== strpos($route, ':' . $key)) { - $route = str_replace(':' . $key, $val, $route); - } - } - } - - // 绑定模型数据 - if (isset($option['bind_model'])) { - $bind = []; - foreach ($option['bind_model'] as $key => $val) { - if ($val instanceof \Closure) { - $result = call_user_func_array($val, [$matches]); - } else { - if (is_array($val)) { - $fields = explode('&', $val[1]); - $model = $val[0]; - $exception = isset($val[2]) ? $val[2] : true; - } else { - $fields = ['id']; - $model = $val; - $exception = true; - } - $where = []; - $match = true; - foreach ($fields as $field) { - if (!isset($matches[$field])) { - $match = false; - break; - } else { - $where[$field] = $matches[$field]; - } - } - if ($match) { - $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); - $result = $query->failException($exception)->find(); - } - } - if (!empty($result)) { - $bind[$key] = $result; - } - } - $request->bind($bind); - } - - if (!empty($option['response'])) { - Hook::add('response_send', $option['response']); - } - - // 解析额外参数 - self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); - // 记录匹配的路由信息 - $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); - - // 检测路由after行为 - if (!empty($option['after_behavior'])) { - if ($option['after_behavior'] instanceof \Closure) { - $result = call_user_func_array($option['after_behavior'], []); - } else { - foreach ((array) $option['after_behavior'] as $behavior) { - $result = Hook::exec($behavior, ''); - if (!is_null($result)) { - break; - } - } - } - // 路由规则重定向 - if ($result instanceof Response) { - return ['type' => 'response', 'response' => $result]; - } elseif (is_array($result)) { - return $result; - } - } - - if ($route instanceof \Closure) { - // 执行闭包 - $result = ['type' => 'function', 'function' => $route]; - } elseif (0 === strpos($route, '/') || strpos($route, '://')) { - // 路由到重定向地址 - $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; - } elseif (false !== strpos($route, '\\')) { - // 路由到方法 - list($path, $var) = self::parseUrlPath($route); - $route = str_replace('/', '@', implode('/', $path)); - $method = strpos($route, '@') ? explode('@', $route) : $route; - $result = ['type' => 'method', 'method' => $method, 'var' => $var]; - } elseif (0 === strpos($route, '@')) { - // 路由到控制器 - $route = substr($route, 1); - list($route, $var) = self::parseUrlPath($route); - $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; - $request->action(array_pop($route)); - $request->controller($route ? array_pop($route) : Config::get('default_controller')); - $request->module($route ? array_pop($route) : Config::get('default_module')); - App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); - } else { - // 路由到模块/控制器/操作 - $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false); - } - // 开启请求缓存 - if ($request->isGet() && isset($option['cache'])) { - $cache = $option['cache']; - if (is_array($cache)) { - list($key, $expire, $tag) = array_pad($cache, 3, null); - } else { - $key = str_replace('|', '/', $pathinfo); - $expire = $cache; - $tag = null; - } - $request->cache($key, $expire, $tag); - } - return $result; - } - - /** - * 解析URL地址为 模块/控制器/操作 - * @access private - * @param string $url URL地址 - * @param bool $convert 是否自动转换URL地址 - * @return array - */ - private static function parseModule($url, $convert = false) - { - list($path, $var) = self::parseUrlPath($url); - $action = array_pop($path); - $controller = !empty($path) ? array_pop($path) : null; - $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; - $method = Request::instance()->method(); - if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { - // 操作方法前缀支持 - $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; - } - // 设置当前请求的路由变量 - Request::instance()->route($var); - // 路由到模块/控制器/操作 - return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert]; - } - - /** - * 解析URL地址中的参数Request对象 - * @access private - * @param string $url 路由规则 - * @param array $var 变量 * @return void */ - private static function parseUrlParams($url, &$var = []) + public function clear() { - if ($url) { - if (Config::get('url_param_type')) { - $var += explode('|', $url); - } else { - preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { - $var[$match[1]] = strip_tags($match[2]); - }, $url); - } - } - // 设置当前请求的参数 - Request::instance()->route($var); - } - - // 分析路由规则中的变量 - private static function parseVar($rule) - { - // 提取路由规则中的变量 - $var = []; - foreach (explode('/', $rule) as $val) { - $optional = false; - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $optional = true; - } else { - $optional = false; - } - $var[$name] = $optional ? 2 : 1; - } - } - - if (0 === strpos($val, '[:')) { - // 可选参数 - $optional = true; - $val = substr($val, 1, -1); - } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - $var[$name] = $optional ? 2 : 1; - } - } - return $var; + $this->app['rule_name']->clear(); + $this->group->clear(); } /** - * 获取路由解析缓存的key - * @param Request $request - * @return string + * 设置全局的路由分组参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup */ - private static function getCheckCacheKey(Request $request) + public function __call($method, $args) { - static $key; + return call_user_func_array([$this->group, $method], $args); + } - if (empty($key)) { - if ($callback = Config::get('route_check_cache_key')) { - $key = call_user_func($callback, $request); - } else { - $key = "{$request->host(true)}|{$request->method()}|{$request->path()}"; - } - } + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request']); - return $key; + return $data; } } diff --git a/Server/thinkphp/library/think/Session.php b/Server/thinkphp/library/think/Session.php index 61150bca..63ee7a03 100644 --- a/Server/thinkphp/library/think/Session.php +++ b/Server/thinkphp/library/think/Session.php @@ -15,37 +15,115 @@ use think\exception\ClassNotFoundException; class Session { - protected static $prefix = ''; - protected static $init = null; + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 是否初始化 + * @var bool + */ + protected $init = null; + + /** + * 锁驱动 + * @var object + */ + protected $lockDriver = null; + + /** + * 锁key + * @var string + */ + protected $sessKey = 'PHPSESSID'; + + /** + * 锁超时时间 + * @var integer + */ + protected $lockTimeout = 3; + + /** + * 是否启用锁机制 + * @var bool + */ + protected $lock = false; + + public function __construct(array $config = []) + { + $this->config = $config; + } /** * 设置或者获取session作用域(前缀) - * @param string $prefix + * @access public + * @param string $prefix * @return string|void */ - public static function prefix($prefix = '') + public function prefix($prefix = '') { - empty(self::$init) && self::boot(); + empty($this->init) && $this->boot(); + if (empty($prefix) && null !== $prefix) { - return self::$prefix; + return $this->prefix; } else { - self::$prefix = $prefix; + $this->prefix = $prefix; + } + } + + public static function __make(Config $config) + { + return new static($config->pull('session')); + } + + /** + * 配置 + * @access public + * @param array $config + * @return void + */ + public function setConfig(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; + } + + if (isset($config['use_lock'])) { + $this->lock = $config['use_lock']; } } + /** + * 设置已经初始化 + * @access public + * @return void + */ + public function inited() + { + $this->init = true; + } + /** * session初始化 - * @param array $config + * @access public + * @param array $config * @return void * @throws \think\Exception */ - public static function init(array $config = []) + public function init(array $config = []) { - if (empty($config)) { - $config = Config::get('session'); - } - // 记录初始化信息 - App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); + $config = $config ?: $this->config; + $isDoStart = false; if (isset($config['use_trans_sid'])) { ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); @@ -57,42 +135,57 @@ class Session $isDoStart = true; } - if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) { - self::$prefix = $config['prefix']; + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; } + + if (isset($config['use_lock'])) { + $this->lock = $config['use_lock']; + } + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { session_id($_REQUEST[$config['var_session_id']]); } elseif (isset($config['id']) && !empty($config['id'])) { session_id($config['id']); } + if (isset($config['name'])) { session_name($config['name']); } + if (isset($config['path'])) { session_save_path($config['path']); } + if (isset($config['domain'])) { ini_set('session.cookie_domain', $config['domain']); } + if (isset($config['expire'])) { ini_set('session.gc_maxlifetime', $config['expire']); ini_set('session.cookie_lifetime', $config['expire']); } + if (isset($config['secure'])) { ini_set('session.cookie_secure', $config['secure']); } + if (isset($config['httponly'])) { ini_set('session.cookie_httponly', $config['httponly']); } + if (isset($config['use_cookies'])) { ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); } + if (isset($config['cache_limiter'])) { session_cache_limiter($config['cache_limiter']); } + if (isset($config['cache_expire'])) { session_cache_expire($config['cache_expire']); } + if (!empty($config['type'])) { // 读取session驱动 $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); @@ -102,42 +195,51 @@ class Session throw new ClassNotFoundException('error session handler:' . $class, $class); } } + if ($isDoStart) { - session_start(); - self::$init = true; + $this->start(); } else { - self::$init = false; + $this->init = false; } + + return $this; } /** * session自动启动或者初始化 + * @access public * @return void */ - public static function boot() + public function boot() { - if (is_null(self::$init)) { - self::init(); - } elseif (false === self::$init) { + if (is_null($this->init)) { + $this->init(); + } + + if (false === $this->init) { if (PHP_SESSION_ACTIVE != session_status()) { - session_start(); + $this->start(); } - self::$init = true; + $this->init = true; } } /** * session设置 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function set($name, $value = '', $prefix = null) + public function set($name, $value, $prefix = null) { - empty(self::$init) && self::boot(); + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; - $prefix = !is_null($prefix) ? $prefix : self::$prefix; if (strpos($name, '.')) { // 二维数组赋值 list($name1, $name2) = explode('.', $name); @@ -151,51 +253,130 @@ class Session } else { $_SESSION[$name] = $value; } + + $this->unlock(); } /** * session获取 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) * @return mixed */ - public static function get($name = '', $prefix = null) + public function get($name = '', $prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if ('' == $name) { - // 获取全部的session - $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; - } elseif ($prefix) { - // 获取session - if (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null; - } else { - $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null; - } - } else { - if (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; - } else { - $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null; + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + + if ('' != $name) { + $name = explode('.', $name); + + foreach ($name as $val) { + if (isset($value[$val])) { + $value = $value[$val]; + } else { + $value = null; + break; + } } } + + $this->unlock(); + return $value; } + /** + * session 读写锁驱动实例化 + */ + protected function initDriver() + { + $config = $this->config; + + if (!empty($config['type']) && isset($config['use_lock']) && $config['use_lock']) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类及类中是否存在 lock 和 unlock 函数 + if (class_exists($class) && method_exists($class, 'lock') && method_exists($class, 'unlock')) { + $this->lockDriver = new $class($config); + } + } + + // 通过cookie获得session_id + if (isset($config['name']) && $config['name']) { + $this->sessKey = $config['name']; + } + + if (isset($config['lock_timeout']) && $config['lock_timeout'] > 0) { + $this->lockTimeout = $config['lock_timeout']; + } + } + + /** + * session 读写加锁 + * @access protected + * @return void + */ + protected function lock() + { + if (empty($this->lock)) { + return; + } + + $this->initDriver(); + + if (null !== $this->lockDriver && method_exists($this->lockDriver, 'lock')) { + $t = time(); + // 使用 session_id 作为互斥条件,即只对同一 session_id 的会话互斥。第一次请求没有 session_id + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + + do { + if (time() - $t > $this->lockTimeout) { + $this->unlock(); + } + } while (!$this->lockDriver->lock($sessID, $this->lockTimeout)); + } + } + + /** + * session 读写解锁 + * @access protected + * @return void + */ + protected function unlock() + { + if (empty($this->lock)) { + return; + } + + $this->pause(); + + if ($this->lockDriver && method_exists($this->lockDriver, 'unlock')) { + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + $this->lockDriver->unlock($sessID); + } + } + /** * session获取并删除 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) * @return mixed */ - public static function pull($name, $prefix = null) + public function pull($name, $prefix = null) { - $result = self::get($name, $prefix); + $result = $this->get($name, $prefix); + if ($result) { - self::delete($name, $prefix); + $this->delete($name, $prefix); return $result; } else { return; @@ -204,53 +385,63 @@ class Session /** * session设置 下一次请求有效 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function flash($name, $value) + public function flash($name, $value) { - self::set($name, $value); - if (!self::has('__flash__.__time__')) { - self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + $this->set($name, $value); + + if (!$this->has('__flash__.__time__')) { + $this->set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); } - self::push('__flash__', $name); + + $this->push('__flash__', $name); } /** * 清空当前请求的session数据 + * @access public * @return void */ - public static function flush() + public function flush() { - if (self::$init) { - $item = self::get('__flash__'); + if (!$this->init) { + return; + } - if (!empty($item)) { - $time = $item['__time__']; - if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { - unset($item['__time__']); - self::delete($item); - self::set('__flash__', []); - } + $item = $this->get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + $this->delete($item); + $this->set('__flash__', []); } } } /** * 删除session数据 - * @param string|array $name session名称 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function delete($name, $prefix = null) + public function delete($name, $prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if (is_array($name)) { foreach ($name as $key) { - self::delete($key, $prefix); + $this->delete($key, $prefix); } } elseif (strpos($name, '.')) { list($name1, $name2) = explode('.', $name); @@ -270,13 +461,15 @@ class Session /** * 清空session数据 - * @param string|null $prefix 作用域(前缀) + * @access public + * @param string|null $prefix 作用域(前缀) * @return void */ - public static function clear($prefix = null) + public function clear($prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if ($prefix) { unset($_SESSION[$prefix]); } else { @@ -286,81 +479,101 @@ class Session /** * 判断session数据 - * @param string $name session名称 - * @param string|null $prefix + * @access public + * @param string $name session名称 + * @param string|null $prefix * @return bool */ - public static function has($name, $prefix = null) + public function has($name, $prefix = null) { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (strpos($name, '.')) { - // 支持数组 - list($name1, $name2) = explode('.', $name); - return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); - } else { - return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + + $name = explode('.', $name); + + foreach ($name as $val) { + if (!isset($value[$val])) { + return false; + } else { + $value = $value[$val]; + } } + + return true; } /** * 添加数据到一个session数组 + * @access public * @param string $key * @param mixed $value * @return void */ - public static function push($key, $value) + public function push($key, $value) { - $array = self::get($key); + $array = $this->get($key); + if (is_null($array)) { $array = []; } + $array[] = $value; - self::set($key, $array); + + $this->set($key, $array); } /** * 启动session + * @access public * @return void */ - public static function start() + public function start() { session_start(); - self::$init = true; + + $this->init = true; } /** * 销毁session + * @access public * @return void */ - public static function destroy() + public function destroy() { if (!empty($_SESSION)) { $_SESSION = []; } + session_unset(); session_destroy(); - self::$init = null; + + $this->init = null; + $this->lockDriver = null; } /** * 重新生成session_id - * @param bool $delete 是否删除关联会话文件 + * @access public + * @param bool $delete 是否删除关联会话文件 * @return void */ - public static function regenerate($delete = false) + public function regenerate($delete = false) { session_regenerate_id($delete); } /** * 暂停session + * @access public * @return void */ - public static function pause() + public function pause() { // 暂停session session_write_close(); - self::$init = false; + $this->init = false; } } diff --git a/Server/thinkphp/library/think/Template.php b/Server/thinkphp/library/think/Template.php index 9ba0ff35..2855cbcb 100644 --- a/Server/thinkphp/library/think/Template.php +++ b/Server/thinkphp/library/think/Template.php @@ -12,7 +12,6 @@ namespace think; use think\exception\TemplateNotFoundException; -use think\template\TagLib; /** * ThinkPHP分离出来的模板引擎 @@ -21,14 +20,22 @@ use think\template\TagLib; */ class Template { - // 模板变量 + protected $app; + /** + * 模板变量 + * @var array + */ protected $data = []; - // 引擎配置 + + /** + * 模板配置参数 + * @var array + */ protected $config = [ 'view_path' => '', // 模板路径 'view_base' => '', 'view_suffix' => 'html', // 默认模板文件后缀 - 'view_depr' => DS, + 'view_depr' => DIRECTORY_SEPARATOR, 'cache_suffix' => 'php', // 默认模板缓存后缀 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 @@ -51,20 +58,36 @@ class Template 'cache_id' => '', // 模板缓存ID 'tpl_replace_string' => [], 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 ]; - private $literal = []; - private $includeFile = []; // 记录所有模板包含的文件路径及更新时间 + /** + * 保留内容信息 + * @var array + */ + private $literal = []; + + /** + * 模板包含信息 + * @var array + */ + private $includeFile = []; + + /** + * 模板存储对象 + * @var object + */ protected $storage; /** - * 构造函数 + * 架构函数 * @access public - * @param array $config + * @param array $config */ - public function __construct(array $config = []) + public function __construct(App $app, array $config = []) { - $this->config['cache_path'] = TEMP_PATH; + $this->app = $app; + $this->config['cache_path'] = $app->getRuntimePath() . 'temp/'; $this->config = array_merge($this->config, $config); $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; @@ -76,16 +99,21 @@ class Template $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); // 初始化模板编译存储器 - $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); - $this->storage = new $class(); + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + + $this->storage = Loader::factory($type, '\\think\\template\\driver\\', null); + } + + public static function __make(Config $config) + { + return new static($config->pull('template')); } /** * 模板变量赋值 * @access public - * @param mixed $name - * @param mixed $value + * @param mixed $name + * @param mixed $value * @return void */ public function assign($name, $value = '') @@ -100,8 +128,8 @@ class Template /** * 模板引擎参数赋值 * @access public - * @param mixed $name - * @param mixed $value + * @param mixed $name + * @param mixed $value */ public function __set($name, $value) { @@ -111,8 +139,8 @@ class Template /** * 模板引擎配置项 * @access public - * @param array|string $config - * @return string|void|array + * @param array|string $config + * @return void|array */ public function config($config) { @@ -120,8 +148,6 @@ class Template $this->config = array_merge($this->config, $config); } elseif (isset($this->config[$config])) { return $this->config[$config]; - } else { - return; } } @@ -135,26 +161,28 @@ class Template { if ('' == $name) { return $this->data; - } else { - $data = $this->data; - foreach (explode('.', $name) as $key => $val) { - if (isset($data[$val])) { - $data = $data[$val]; - } else { - $data = null; - break; - } - } - return $data; } + + $data = $this->data; + + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + + return $data; } /** * 渲染模板文件 * @access public - * @param string $template 模板文件 - * @param array $vars 模板变量 - * @param array $config 模板参数 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 * @return void */ public function fetch($template, $vars = [], $config = []) @@ -162,36 +190,49 @@ class Template if ($vars) { $this->data = $vars; } + if ($config) { $this->config($config); } + + $cache = $this->app['cache']; + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 读取渲染缓存 - $cacheContent = Cache::get($this->config['cache_id']); + $cacheContent = $cache->get($this->config['cache_id']); + if (false !== $cacheContent) { echo $cacheContent; return; } } + $template = $this->parseTemplateFile($template); + if ($template) { - $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { // 缓存无效 重新模板编译 $content = file_get_contents($template); $this->compiler($content, $cacheFile); } + // 页面缓存 ob_start(); ob_implicit_flush(0); + // 读取编译存储 $this->storage->read($cacheFile, $this->data); + // 获取并清空缓存 $content = ob_get_clean(); + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 缓存页面输出 - Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); + $cache->set($this->config['cache_id'], $content, $this->config['cache_time']); } + echo $content; } } @@ -199,9 +240,9 @@ class Template /** * 渲染模板内容 * @access public - * @param string $content 模板内容 - * @param array $vars 模板变量 - * @param array $config 模板参数 + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 * @return void */ public function display($content, $vars = [], $config = []) @@ -209,14 +250,18 @@ class Template if ($vars) { $this->data = $vars; } + if ($config) { $this->config($config); } + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { // 缓存无效 模板编译 $this->compiler($content, $cacheFile); } + // 读取编译存储 $this->storage->read($cacheFile, $this->data); } @@ -224,9 +269,9 @@ class Template /** * 设置布局 * @access public - * @param mixed $name 布局模板名称 false 则关闭布局 - * @param string $replace 布局模板内容替换标识 - * @return Template + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return object */ public function layout($name, $replace = '') { @@ -236,14 +281,17 @@ class Template } else { // 开启布局 $this->config['layout_on'] = true; + // 名称必须为字符串 if (is_string($name)) { $this->config['layout_name'] = $name; } + if (!empty($replace)) { $this->config['layout_item'] = $replace; } } + return $this; } @@ -251,32 +299,28 @@ class Template * 检查编译缓存是否有效 * 如果无效则需要重新编译 * @access private - * @param string $cacheFile 缓存文件名 + * @param string $cacheFile 缓存文件名 * @return boolean */ private function checkCache($cacheFile) { - // 未开启缓存功能 - if (!$this->config['tpl_cache']) { - return false; - } - // 缓存文件不存在 - if (!is_file($cacheFile)) { - return false; - } - // 读取缓存文件失败 - if (!$handle = @fopen($cacheFile, "r")) { + if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) { return false; } + // 读取第一行 preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + if (!isset($matches[1])) { return false; } + $includeFile = unserialize($matches[1]); + if (!is_array($includeFile)) { return false; } + // 检查模板文件是否有更新 foreach ($includeFile as $path => $time) { if (is_file($path) && filemtime($path) > $time) { @@ -284,6 +328,7 @@ class Template return false; } } + // 检查编译存储是否有效 return $this->storage->check($cacheFile, $this->config['cache_time']); } @@ -291,23 +336,24 @@ class Template /** * 检查编译缓存是否存在 * @access public - * @param string $cacheId 缓存的id + * @param string $cacheId 缓存的id * @return boolean */ public function isCache($cacheId) { if ($cacheId && $this->config['display_cache']) { // 缓存页面输出 - return Cache::has($cacheId); + return $this->app['cache']->has($cacheId); } + return false; } /** * 编译模板文件内容 * @access private - * @param string $content 模板内容 - * @param string $cacheFile 缓存文件名 + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 * @return void */ private function compiler(&$content, $cacheFile) @@ -320,6 +366,7 @@ class Template } else { // 读取布局模板 $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + if ($layoutFile) { // 替换布局的主体内容 $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); @@ -331,30 +378,34 @@ class Template // 模板解析 $this->parse($content); + if ($this->config['strip_space']) { /* 去除html空格与换行 */ $find = ['~>\s+<~', '~>(\s+\n|\r)~']; $replace = ['><', '>']; $content = preg_replace($find, $replace, $content); } + // 优化生成的php代码 - $content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content); + $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content); + // 模板过滤输出 $replace = $this->config['tpl_replace_string']; $content = str_replace(array_keys($replace), array_values($replace), $content); + // 添加安全代码及模板引用记录 - $content = 'includeFile) . '*/ ?>' . "\n" . $content; + $content = 'includeFile) . '*/ ?>' . "\n" . $content; // 编译存储 $this->storage->write($cacheFile, $content); + $this->includeFile = []; - return; } /** * 模板解析入口 * 支持普通标签和TagLib解析 支持自定义标签库 * @access public - * @param string $content 要解析的模板内容 + * @param string $content 要解析的模板内容 * @return void */ public function parse(&$content) @@ -363,16 +414,22 @@ class Template if (empty($content)) { return; } + // 替换literal标签内容 $this->parseLiteral($content); + // 解析继承 $this->parseExtend($content); + // 解析布局 $this->parseLayout($content); + // 检查include语法 $this->parseInclude($content); + // 替换包含文件中literal标签内容 $this->parseLiteral($content); + // 检查PHP语法 $this->parsePhp($content); @@ -383,6 +440,7 @@ class Template // 当TAGLIB_LOAD配置为true时才会进行检测 if ($this->config['taglib_load']) { $tagLibs = $this->getIncludeTagLib($content); + if (!empty($tagLibs)) { // 对导入的TagLib进行解析 foreach ($tagLibs as $tagLibName) { @@ -390,30 +448,34 @@ class Template } } } + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 if ($this->config['taglib_pre_load']) { $tagLibs = explode(',', $this->config['taglib_pre_load']); + foreach ($tagLibs as $tag) { $this->parseTagLib($tag, $content); } } + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 $tagLibs = explode(',', $this->config['taglib_build_in']); + foreach ($tagLibs as $tag) { $this->parseTagLib($tag, $content, true); } + // 解析普通模板标签 {$tagName} $this->parseTag($content); // 还原被替换的Literal标签 $this->parseLiteral($content, true); - return; } /** * 检查PHP语法 * @access private - * @param string $content 要解析的模板内容 + * @param string $content 要解析的模板内容 * @return void * @throws \think\Exception */ @@ -421,17 +483,17 @@ class Template { // 短标签的情况要将' . "\n", $content); + // PHP语法检查 if ($this->config['tpl_deny_php'] && false !== strpos($content, 'parseAttr($matches[0]); + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { // 读取布局模板 $layoutFile = $this->parseTemplateFile($array['name']); + if ($layoutFile) { $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; // 替换布局的主体内容 @@ -454,7 +518,6 @@ class Template } else { $content = str_replace('{__NOLAYOUT__}', '', $content); } - return; } /** @@ -472,15 +535,19 @@ class Template $array = $this->parseAttr($match[0]); $file = $array['file']; unset($array['file']); + // 分析模板文件名并读取内容 $parseStr = $this->parseTemplateName($file); + foreach ($array as $k => $v) { // 以$开头字符串转换成模板变量 if (0 === strpos($v, '$')) { $v = $this->get(substr($v, 1)); } + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); } + $content = str_replace($match[0], $parseStr, $content); // 再次对包含文件进行模板分析 $func($parseStr); @@ -488,9 +555,9 @@ class Template unset($matches); } }; + // 替换模板中的include标签 $func($content); - return; } /** @@ -504,21 +571,26 @@ class Template $regex = $this->getRegex('extend'); $array = $blocks = $baseBlocks = []; $extend = ''; - $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { if (preg_match($regex, $template, $matches)) { if (!isset($array[$matches['name']])) { $array[$matches['name']] = 1; // 读取继承模板 $extend = $this->parseTemplateName($matches['name']); + // 递归检查继承 $func($extend); + // 取得block标签内容 $blocks = array_merge($blocks, $this->parseBlock($template)); + return; } } else { // 取得顶层模板block标签内容 $baseBlocks = $this->parseBlock($template, true); + if (empty($extend)) { // 无extend标签但有block标签的情况 $extend = $template; @@ -527,28 +599,35 @@ class Template }; $func($content); + if (!empty($extend)) { if ($baseBlocks) { $children = []; foreach ($baseBlocks as $name => $val) { $replace = $val['content']; + if (!empty($children[$name])) { // 如果包含有子block标签 foreach ($children[$name] as $key) { $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); } } + if (isset($blocks[$name])) { // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + if (!empty($val['parent'])) { // 如果不是最顶层的block标签 $parent = $val['parent']; + if (isset($blocks[$parent])) { $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); } + $blocks[$name]['content'] = $replace; $children[$parent][] = $name; + continue; } } elseif (!empty($val['parent'])) { @@ -556,16 +635,17 @@ class Template $children[$val['parent']][] = $name; $blocks[$name] = $val; } + if (!$val['parent']) { // 替换模板中的顶级block标签 $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); } } } + $content = $extend; unset($blocks, $baseBlocks); } - return; } /** @@ -578,9 +658,11 @@ class Template private function parseLiteral(&$content, $restore = false) { $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { if (!$restore) { $count = count($this->literal); + // 替换literal标签 foreach ($matches as $match) { $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); @@ -592,12 +674,13 @@ class Template foreach ($matches as $match) { $content = str_replace($match[0], $this->literal[$match[1]], $content); } + // 清空literal记录 $this->literal = []; } + unset($matches); } - return; } /** @@ -611,20 +694,24 @@ class Template { $regex = $this->getRegex('block'); $result = []; + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { $right = $keys = []; + foreach ($matches as $match) { if (empty($match['name'][0])) { if (count($right) > 0) { - $tag = array_pop($right); - $start = $tag['offset'] + strlen($tag['tag']); - $length = $match[0][1] - $start; + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + $result[$tag['name']] = [ 'begin' => $tag['tag'], 'content' => substr($content, $start, $length), 'end' => $match[0][0], 'parent' => count($right) ? end($right)['name'] : '', ]; + $keys[$tag['name']] = $match[0][1]; } } else { @@ -636,12 +723,15 @@ class Template ]; } } + unset($right, $matches); + if ($sort) { // 按block标签结束符在模板中的位置排序 array_multisort($keys, $result); } } + return $result; } @@ -658,9 +748,9 @@ class Template if (preg_match($this->getRegex('taglib'), $content, $matches)) { // 替换TagLib标签 $content = str_replace($matches[0], '', $content); + return explode(',', $matches['name']); } - return; } /** @@ -680,10 +770,10 @@ class Template } else { $className = '\\think\\template\\taglib\\' . ucwords($tagLib); } - /** @var Taglib $tLib */ + $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); - return; } /** @@ -697,17 +787,19 @@ class Template { $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; $array = []; + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $array[$match['name']] = $match['value']; } unset($matches); } + if (!empty($name) && isset($array[$name])) { return $array[$name]; - } else { - return $array; } + + return $array; } /** @@ -720,10 +812,12 @@ class Template private function parseTag(&$content) { $regex = $this->getRegex('tag'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $str = stripslashes($match[1]); $flag = substr($str, 0, 1); + switch ($flag) { case '$': // 解析模板变量 格式 {$varName} @@ -731,20 +825,24 @@ class Template if (false !== $pos = strpos($str, '?')) { $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); $name = $array[0]; + $this->parseVar($name); - $this->parseVarFunction($name); + //$this->parseVarFunction($name); $str = trim(substr($str, $pos + 1)); $this->parseVar($str); $first = substr($str, 0, 1); + if (strpos($name, ')')) { // $name为对象或是自动识别,或者含有函数 if (isset($array[1])) { $this->parseVar($array[2]); $name .= $array[1] . $array[2]; } + switch ($first) { case '?': + $this->parseVarFunction($name); $str = ''; break; case '=': @@ -755,27 +853,45 @@ class Template } } else { if (isset($array[1])) { + $express = true; $this->parseVar($array[2]); $express = $name . $array[1] . $array[2]; } else { $express = false; } + + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + // $name为数组 switch ($first) { case '?': // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx - $str = ''; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; case '=': // {$varname?='xxx'} $varname为真时才输出xxx - $str = ''; + $str = ''; break; case ':': // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx - $str = ''; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; default: - $str = ''; + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + } + $str = ''; } } } else { @@ -814,11 +930,12 @@ class Template $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; break; } + $content = str_replace($match[0], $str, $content); } + unset($matches); } - return; } /** @@ -831,10 +948,13 @@ class Template public function parseVar(&$varStr) { $varStr = trim($varStr); + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { static $_varParseList = []; + while ($matches[0]) { $match = array_pop($matches[0]); + //如果已经解析过该变量字串,则直接返回变量值 if (isset($_varParseList[$match[0]])) { $parseStr = $_varParseList[$match[0]]; @@ -842,6 +962,7 @@ class Template if (strpos($match[0], '.')) { $vars = explode('.', $match[0]); $first = array_shift($vars); + if ('$Think' == $first) { // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 $parseStr = $this->parseThinkVar($vars); @@ -856,7 +977,8 @@ class Template } else { $params = ''; } - $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')'; + + $parseStr = 'app(\'request\')->' . $method . '(' . $params . ')'; } else { switch ($this->config['tpl_var_identify']) { case 'array': // 识别为数组 @@ -872,45 +994,81 @@ class Template } else { $parseStr = str_replace(':', '->', $match[0]); } + $_varParseList[$match[0]] = $parseStr; } + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); } unset($matches); } - return; } /** * 对模板中使用了函数的变量进行解析 * 格式 {$varname|function1|function2=arg1,arg2} * @access public - * @param string $varStr 变量字符串 + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 * @return void */ - public function parseVarFunction(&$varStr) + public function parseVarFunction(&$varStr, $autoescape = true) { - if (false == strpos($varStr, '|')) { - return; + if (!$autoescape && false === strpos($varStr, '|')) { + return $varStr; + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { + $varStr .= '|' . $this->config['default_filter']; } + static $_varFunctionList = []; - $_key = md5($varStr); + + $_key = md5($varStr); + //如果已经解析过该变量字串,则直接返回变量值 if (isset($_varFunctionList[$_key])) { $varStr = $_varFunctionList[$_key]; } else { $varArray = explode('|', $varStr); + // 取得变量名称 - $name = array_shift($varArray); + $name = trim(array_shift($varArray)); + // 对变量使用函数 $length = count($varArray); + // 取得模板禁止使用函数列表 $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + for ($i = 0; $i < $length; $i++) { $args = explode('=', $varArray[$i], 2); + // 模板函数过滤 $fun = trim($args[0]); - switch ($fun) { + if (in_array($fun, $template_deny_funs)) { + continue; + } + + switch (strtolower($fun)) { + case 'raw': + break; + case 'date': + $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; case 'default': // 特殊模板函数 if (false === strpos($name, '(')) { $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; @@ -919,26 +1077,25 @@ class Template } break; default: // 通用模板函数 - if (!in_array($fun, $template_deny_funs)) { - if (isset($args[1])) { - if (strstr($args[1], '###')) { - $args[1] = str_replace('###', $name, $args[1]); - $name = "$fun($args[1])"; - } else { - $name = "$fun($name,$args[1])"; - } + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; } else { - if (!empty($args[0])) { - $name = "$fun($name)"; - } + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; } } } } + $_varFunctionList[$_key] = $name; $varStr = $name; } - return; + return $varStr; } /** @@ -952,37 +1109,38 @@ class Template { $type = strtoupper(trim(array_shift($vars))); $param = implode('.', $vars); + if ($vars) { switch ($type) { case 'SERVER': - $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->server(\'' . $param . '\')'; break; case 'GET': - $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->get(\'' . $param . '\')'; break; case 'POST': - $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->post(\'' . $param . '\')'; break; case 'COOKIE': - $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; + $parseStr = 'app(\'cookie\')->get(\'' . $param . '\')'; break; case 'SESSION': - $parseStr = '\\think\\Session::get(\'' . $param . '\')'; + $parseStr = 'app(\'session\')->get(\'' . $param . '\')'; break; case 'ENV': - $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->env(\'' . $param . '\')'; break; case 'REQUEST': - $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; + $parseStr = 'app(\'request\')->request(\'' . $param . '\')'; break; case 'CONST': $parseStr = strtoupper($param); break; case 'LANG': - $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; + $parseStr = 'app(\'lang\')->get(\'' . $param . '\')'; break; case 'CONFIG': - $parseStr = '\\think\\Config::get(\'' . $param . '\')'; + $parseStr = 'app(\'config\')->get(\'' . $param . '\')'; break; default: $parseStr = '\'\''; @@ -994,7 +1152,7 @@ class Template $parseStr = "date('Y-m-d g:i a',time())"; break; case 'VERSION': - $parseStr = 'THINK_VERSION'; + $parseStr = 'app()->version()'; break; case 'LDELIM': $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; @@ -1010,6 +1168,7 @@ class Template } } } + return $parseStr; } @@ -1023,20 +1182,25 @@ class Template { $array = explode(',', $templateName); $parseStr = ''; + foreach ($array as $templateName) { if (empty($templateName)) { continue; } + if (0 === strpos($templateName, '$')) { //支持加载变量文件名 $templateName = $this->get(substr($templateName, 1)); } + $template = $this->parseTemplateFile($templateName); + if ($template) { // 获取模板文件内容 $parseStr .= file_get_contents($template); } } + return $parseStr; } @@ -1052,27 +1216,31 @@ class Template if (strpos($template, '@')) { list($module, $template) = explode('@', $template); } + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $this->config['view_depr'], $template); } else { $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); } + if ($this->config['view_base']) { - $module = isset($module) ? $module : Request::instance()->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); + $module = isset($module) ? $module : $this->app['request']->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; } - $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.')); + + $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); } if (is_file($template)) { // 记录模板文件的更新时间 $this->includeFile[$template] = filemtime($template); + return $template; - } else { - throw new TemplateNotFoundException('template not exists:' . $template, $template); } + + throw new TemplateNotFoundException('template not exists:' . $template, $template); } /** @@ -1087,6 +1255,7 @@ class Template if ('tag' == $tagName) { $begin = $this->config['tpl_begin']; $end = $this->config['tpl_end']; + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; } else { @@ -1096,12 +1265,13 @@ class Template $begin = $this->config['taglib_begin']; $end = $this->config['taglib_end']; $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + switch ($tagName) { case 'block': if ($single) { - $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; } else { - $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; } break; case 'literal': @@ -1127,13 +1297,22 @@ class Template $name = 'name'; } if ($single) { - $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; } else { - $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; } break; } } + return '/' . $regex . '/is'; } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['storage']); + + return $data; + } } diff --git a/Server/thinkphp/library/think/Url.php b/Server/thinkphp/library/think/Url.php index 53a545f9..acd510aa 100644 --- a/Server/thinkphp/library/think/Url.php +++ b/Server/thinkphp/library/think/Url.php @@ -13,39 +13,88 @@ namespace think; class Url { - // 生成URL地址的root - protected static $root; - protected static $bindCheck; + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * ROOT地址 + * @var string + */ + protected $root; + + /** + * 绑定检查 + * @var bool + */ + protected $bindCheck; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = $config; + + if (is_file($app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $app['route']->setName(include $app->getRuntimePath() . 'route.php'); + } + } + + /** + * 初始化 + * @access public + * @param array $config + * @return void + */ + public function init(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('app')); + } /** * URL生成 支持路由反射 - * @param string $url 路由地址 - * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] - * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 - * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @access public + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 * @return string */ - public static function build($url = '', $vars = '', $suffix = true, $domain = false) + public function build($url = '', $vars = '', $suffix = true, $domain = false) { - if (false === $domain && Route::rules('domain')) { - $domain = true; - } // 解析URL if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { // [name] 表示使用路由命名标识生成URL $name = substr($url, 1, $pos - 1); $url = 'name' . substr($url, $pos + 1); } + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { $info = parse_url($url); $url = !empty($info['path']) ? $info['path'] : ''; + if (isset($info['fragment'])) { // 解析锚点 $anchor = $info['fragment']; + if (false !== strpos($anchor, '?')) { // 解析参数 list($anchor, $info['query']) = explode('?', $anchor, 2); } + if (false !== strpos($anchor, '@')) { // 解析域名 list($anchor, $domain) = explode('@', $anchor, 2); @@ -63,23 +112,28 @@ class Url } if ($url) { - $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->app['route']->getName($checkName, $checkDomain); + if (is_null($rule) && isset($info['query'])) { - $rule = Route::name($url); + $rule = $this->app['route']->getName($url); // 解析地址里面参数 合并到vars parse_str($info['query'], $params); $vars = array_merge($params, $vars); unset($info['query']); } } - if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { // 匹配路由命名标识 $url = $match[0]; - // 替换可选分隔符 - $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); - if (!empty($match[1])) { + + if ($domain) { $domain = $match[1]; } + if (!is_null($match[2])) { $suffix = $match[2]; } @@ -87,14 +141,14 @@ class Url throw new \InvalidArgumentException('route name not exists:' . $name); } else { // 检查别名路由 - $alias = Route::rules('alias'); + $alias = $this->app['route']->getAlias(); $matchAlias = false; + if ($alias) { // 别名路由解析 - foreach ($alias as $key => $val) { - if (is_array($val)) { - $val = $val[0]; - } + foreach ($alias as $key => $item) { + $val = $item->getRoute(); + if (0 === strpos($url, $val)) { $url = $key . substr($url, strlen($val)); $matchAlias = true; @@ -102,10 +156,31 @@ class Url } } } + if (!$matchAlias) { // 路由标识不存在 直接解析 - $url = self::parseUrl($url, $domain); + $url = $this->parseUrl($url); } + + // 检测URL绑定 + if (!$this->bindCheck) { + $bind = $this->app['route']->getBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->app['route']->getBind(true); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + } + if (isset($info['query'])) { // 解析地址里面参数 合并到vars parse_str($info['query'], $params); @@ -113,32 +188,29 @@ class Url } } - // 检测URL绑定 - if (!self::$bindCheck) { - $type = Route::getBind('type'); - if ($type) { - $bind = Route::getBind($type); - if ($bind && 0 === strpos($url, $bind)) { - $url = substr($url, strlen($bind) + 1); - } - } - } // 还原URL分隔符 - $depr = Config::get('pathinfo_depr'); + $depr = $this->config['pathinfo_depr']; $url = str_replace('/', $depr, $url); // URL后缀 - $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix); + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + // 锚点 $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 if (!empty($vars)) { // 添加参数 - if (Config::get('url_common_param')) { + if ($this->config['url_common_param']) { $vars = http_build_query($vars); $url .= $suffix . '?' . $vars . $anchor; } else { - $paramType = Config::get('url_param_type'); + $paramType = $this->config['url_param_type']; + foreach ($vars as $var => $val) { if ('' !== trim($val)) { if ($paramType) { @@ -148,24 +220,29 @@ class Url } } } + $url .= $suffix . $anchor; } } else { $url .= $suffix . $anchor; } - // 检测域名 - $domain = self::parseDomain($url, $domain); - // URL组装 - $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); - self::$bindCheck = false; + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + $url = $domain . rtrim($this->root ?: $this->app['request']->root(), '/') . '/' . ltrim($url, '/'); + + $this->bindCheck = false; + return $url; } // 直接解析URL地址 - protected static function parseUrl($url, &$domain) + protected function parseUrl($url) { - $request = Request::instance(); + $request = $this->app['request']; + if (0 === strpos($url, '/')) { // 直接作为路由地址解析 $url = substr($url, 1); @@ -177,42 +254,11 @@ class Url $url = substr($url, 1); } else { // 解析到 模块/控制器/操作 - $module = $request->module(); - $domains = Route::rules('domain'); - if (true === $domain && 2 == substr_count($url, '/')) { - $current = $request->host(); - $match = []; - $pos = []; - foreach ($domains as $key => $item) { - if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) { - $pos[$key] = strlen($item['[bind]'][0]) + 1; - $match[] = $key; - $module = ''; - } - } - if ($match) { - $domain = current($match); - foreach ($match as $item) { - if (0 === strpos($current, $item)) { - $domain = $item; - } - } - self::$bindCheck = true; - $url = substr($url, $pos[$domain]); - } - } elseif ($domain) { - if (isset($domains[$domain]['[bind]'][0])) { - $bindModule = $domains[$domain]['[bind]'][0]; - if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { - $module = ''; - } - } - } - $module = $module ? $module . '/' : ''; - + $module = $request->module(); + $module = $module ? $module . '/' : ''; $controller = $request->controller(); + if ('' == $url) { - // 空字符串输出当前的 模块/控制器/操作 $action = $request->action(); } else { $path = explode('/', $url); @@ -220,28 +266,32 @@ class Url $controller = empty($path) ? $controller : array_pop($path); $module = empty($path) ? $module : array_pop($path) . '/'; } - if (Config::get('url_convert')) { + + if ($this->config['url_convert']) { $action = strtolower($action); $controller = Loader::parseName($controller); } + $url = $module . $controller . '/' . $action; } + return $url; } // 检测域名 - protected static function parseDomain(&$url, $domain) + protected function parseDomain(&$url, $domain) { if (!$domain) { return ''; } - $request = Request::instance(); - $rootDomain = Config::get('url_domain_root'); + + $rootDomain = $this->app['request']->rootDomain(); if (true === $domain) { // 自动判断域名 - $domain = Config::get('app_host') ?: $request->host(); + $domain = $this->config['app_host'] ?: $this->app['request']->host(); + + $domains = $this->app['route']->getDomains(); - $domains = Route::rules('domain'); if ($domains) { $route_domain = array_keys($domains); foreach ($route_domain as $domain_prefix) { @@ -251,6 +301,7 @@ class Url if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { $url = ltrim($url, $rule); $domain = $key; + // 生成对应子域名 if (!empty($rootDomain)) { $domain .= $rootDomain; @@ -260,74 +311,102 @@ class Url if (!empty($rootDomain)) { $domain .= $rootDomain; } + break; } } } } } - - } else { - if (empty($rootDomain)) { - $host = Config::get('app_host') ?: $request->host(); - $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; - } - if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { - $domain .= '.' . $rootDomain; - } + } elseif (0 !== strpos($domain, $rootDomain) && false === strpos($domain, '.')) { + $domain .= '.' . $rootDomain; } + if (false !== strpos($domain, '://')) { $scheme = ''; } else { - $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://'; + $scheme = $this->app['request']->isSsl() || $this->config['is_https'] ? 'https://' : 'http://'; + } + return $scheme . $domain; } // 解析URL后缀 - protected static function parseSuffix($suffix) + protected function parseSuffix($suffix) { if ($suffix) { - $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix; + $suffix = true === $suffix ? $this->config['url_html_suffix'] : $suffix; + if ($pos = strpos($suffix, '|')) { $suffix = substr($suffix, 0, $pos); } } + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; } // 匹配路由地址 - public static function getRuleUrl($rule, &$vars = []) + public function getRuleUrl($rule, &$vars = [], $allowDomain = '') { + $port = $this->app['request']->port(); foreach ($rule as $item) { - list($url, $pattern, $domain, $suffix) = $item; - if (empty($pattern)) { - return [rtrim($url, '$'), $domain, $suffix]; + list($url, $pattern, $domain, $suffix, $method) = $item; + + if (is_string($allowDomain) && $domain != $allowDomain) { + continue; } - $type = Config::get('url_common_param'); + + if ($port && !in_array($port, [80, 443])) { + $domain .= ':' . $port; + } + + if (empty($pattern)) { + return [rtrim($url, '?/-'), $domain, $suffix]; + } + + $type = $this->config['url_common_param']; + $keys = []; + foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); - unset($vars[$key]); - $result = [$url, $domain, $suffix]; + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + $keys[] = $key; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; } elseif (2 == $val) { $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); - $result = [$url, $domain, $suffix]; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; } else { + $result = null; + $keys = []; break; } } + + $vars = array_diff_key($vars, array_flip($keys)); + if (isset($result)) { return $result; } } + return false; } // 指定当前生成URL地址的root - public static function root($root) + public function root($root) { - self::$root = $root; - Request::instance()->root($root); + $this->root = $root; + $this->app['request']->setRoot($root); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; } } diff --git a/Server/thinkphp/library/think/Validate.php b/Server/thinkphp/library/think/Validate.php index 608e1e4a..5fde7f31 100644 --- a/Server/thinkphp/library/think/Validate.php +++ b/Server/thinkphp/library/think/Validate.php @@ -12,36 +12,56 @@ namespace think; use think\exception\ClassNotFoundException; +use think\validate\ValidateRule; class Validate { - // 实例 - protected static $instance; - // 自定义的验证类型 + /** + * 自定义验证类型 + * @var array + */ protected static $type = []; - // 验证类型别名 + /** + * 验证类型别名 + * @var array + */ protected $alias = [ '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', ]; - // 当前验证的规则 + /** + * 当前验证规则 + * @var array + */ protected $rule = []; - // 验证提示信息 + /** + * 验证提示信息 + * @var array + */ protected $message = []; - // 验证字段描述 + + /** + * 验证字段描述 + * @var array + */ protected $field = []; - // 验证规则默认提示信息 + /** + * 默认规则提示 + * @var array + */ protected static $typeMsg = [ 'require' => ':attribute require', + 'must' => ':attribute must', 'number' => ':attribute must be numeric', 'integer' => ':attribute must be integer', 'float' => ':attribute must be float', 'boolean' => ':attribute must be bool', 'email' => ':attribute not a valid email address', + 'mobile' => ':attribute not a valid mobile', 'array' => ':attribute must be a array', 'accepted' => ':attribute must be yes,on or 1', 'date' => ':attribute not a valid datetime', @@ -88,73 +108,135 @@ class Validate 'fileMime' => 'mimetype to upload is not allowed', ]; - // 当前验证场景 + /** + * 当前验证场景 + * @var array + */ protected $currentScene = null; - // 正则表达式 regex = ['zip'=>'\d{6}',...] - protected $regex = []; + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; - // 验证场景 scene = ['edit'=>'name1,name2,...'] + /** + * 内置正则验证规则 + * @var array + */ + protected $defaultRegex = [ + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3-9][0-9]\d{8}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/', + 'zip' => '/\d{6}/', + ]; + + /** + * 验证场景定义 + * @var array + */ protected $scene = []; - // 验证失败错误信息 + /** + * 验证失败错误信息 + * @var array + */ protected $error = []; - // 批量验证 + /** + * 是否批量验证 + * @var bool + */ protected $batch = false; /** - * 构造函数 - * @access public - * @param array $rules 验证规则 - * @param array $message 验证提示信息 - * @param array $field 验证字段描述信息 + * 场景需要验证的规则 + * @var array */ - public function __construct(array $rules = [], $message = [], $field = []) + protected $only = []; + + /** + * 场景需要移除的验证规则 + * @var array + */ + protected $remove = []; + + /** + * 场景需要追加的验证规则 + * @var array + */ + protected $append = []; + + /** + * 验证正则定义 + * @var array + */ + protected $regex = []; + + /** + * 架构函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], array $message = [], array $field = []) { - $this->rule = array_merge($this->rule, $rules); + $this->rule = $rules + $this->rule; $this->message = array_merge($this->message, $message); $this->field = array_merge($this->field, $field); } /** - * 实例化验证 + * 创建一个验证器类 * @access public - * @param array $rules 验证规则 - * @param array $message 验证提示信息 - * @param array $field 验证字段描述信息 + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 * @return Validate */ - public static function make($rules = [], $message = [], $field = []) + public static function make(array $rules = [], array $message = [], array $field = []) { - if (is_null(self::$instance)) { - self::$instance = new self($rules, $message, $field); - } - return self::$instance; + return new self($rules, $message, $field); } /** * 添加字段验证规则 * @access protected - * @param string|array $name 字段名称或者规则数组 - * @param mixed $rule 验证规则 - * @return Validate + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则或者字段描述信息 + * @return $this */ public function rule($name, $rule = '') { if (is_array($name)) { - $this->rule = array_merge($this->rule, $name); + $this->rule = $name + $this->rule; + if (is_array($rule)) { + $this->field = array_merge($this->field, $rule); + } } else { $this->rule[$name] = $rule; } + return $this; } /** - * 注册验证(类型)规则 + * 注册扩展验证(类型)规则 * @access public - * @param string $type 验证规则类型 - * @param mixed $callback callback方法(或闭包) + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) * @return void */ public static function extend($type, $callback = null) @@ -168,9 +250,9 @@ class Validate /** * 设置验证规则的默认提示信息 - * @access protected - * @param string|array $type 验证规则类型名称或者数组 - * @param string $msg 验证提示信息 + * @access public + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 * @return void */ public static function setTypeMsg($type, $msg = null) @@ -185,8 +267,8 @@ class Validate /** * 设置提示信息 * @access public - * @param string|array $name 字段名称 - * @param string $message 提示信息 + * @param string|array $name 字段名称 + * @param string $message 提示信息 * @return Validate */ public function message($name, $message = '') @@ -196,59 +278,119 @@ class Validate } else { $this->message[$name] = $message; } + return $this; } /** * 设置验证场景 * @access public - * @param string|array $name 场景名或者场景设置数组 - * @param mixed $fields 要验证的字段 - * @return Validate + * @param string $name 场景名 + * @return $this */ - public function scene($name, $fields = null) + public function scene($name) { - if (is_array($name)) { - $this->scene = array_merge($this->scene, $name); - }if (is_null($fields)) { - // 设置当前场景 - $this->currentScene = $name; - } else { - // 设置验证场景 - $this->scene[$name] = $fields; - } + // 设置当前场景 + $this->currentScene = $name; + return $this; } /** * 判断是否存在某个验证场景 * @access public - * @param string $name 场景名 + * @param string $name 场景名 * @return bool */ public function hasScene($name) { - return isset($this->scene[$name]); + return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); } /** * 设置批量验证 * @access public - * @param bool $batch 是否批量验证 - * @return Validate + * @param bool $batch 是否批量验证 + * @return $this */ public function batch($batch = true) { $this->batch = $batch; + + return $this; + } + + /** + * 指定需要验证的字段列表 + * @access public + * @param array $fields 字段名 + * @return $this + */ + public function only($fields) + { + $this->only = $fields; + + return $this; + } + + /** + * 移除某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 null 移除所有规则 + * @return $this + */ + public function remove($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->remove[$field] = $rule; + } + + return $this; + } + + /** + * 追加某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 + * @return $this + */ + public function append($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + $this->append($key, $rule); + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->append[$field] = $rule; + } + return $this; } /** * 数据自动验证 * @access public - * @param array $data 数据 - * @param mixed $rules 验证规则 - * @param string $scene 验证场景 + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 * @return bool */ public function check($data, $rules = [], $scene = '') @@ -260,37 +402,18 @@ class Validate $rules = $this->rule; } - // 分析验证规则 - $scene = $this->getScene($scene); - if (is_array($scene)) { - // 处理场景验证字段 - $change = []; - $array = []; - foreach ($scene as $k => $val) { - if (is_numeric($k)) { - $array[] = $val; - } else { - $array[] = $k; - $change[$k] = $val; - } + // 获取场景定义 + $this->getScene($scene); + + foreach ($this->append as $key => $rule) { + if (!isset($rules[$key])) { + $rules[$key] = $rule; + unset($this->append[$key]); } } - foreach ($rules as $key => $item) { - // field => rule1|rule2... field=>['rule1','rule2',...] - if (is_numeric($key)) { - // [field,rule1|rule2,msg1|msg2] - $key = $item[0]; - $rule = $item[1]; - if (isset($item[2])) { - $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; - } else { - $msg = []; - } - } else { - $rule = $item; - $msg = []; - } + foreach ($rules as $key => $rule) { + // field => 'rule1|rule2...' field => ['rule1','rule2',...] if (strpos($key, '|')) { // 字段|描述 用于指定属性名称 list($key, $title) = explode('|', $key); @@ -299,28 +422,21 @@ class Validate } // 场景检测 - if (!empty($scene)) { - if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { - continue; - } elseif (is_array($scene)) { - if (!in_array($key, $array)) { - continue; - } elseif (isset($change[$key])) { - // 重载某个验证规则 - $rule = $change[$key]; - } - } + if (!empty($this->only) && !in_array($key, $this->only)) { + continue; } - // 获取数据 支持二维数组 + // 获取数据 支持多维数组 $value = $this->getDataValue($data, $key); // 字段验证 if ($rule instanceof \Closure) { - // 匿名函数验证 支持传入当前字段和所有字段两个数据 - $result = call_user_func_array($rule, [$value, $data]); + $result = call_user_func_array($rule, [$value, $data, $title, $this]); + } elseif ($rule instanceof ValidateRule) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); } else { - $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + $result = $this->checkItem($key, $value, $rule, $data, $title); } if (true !== $result) { @@ -338,20 +454,23 @@ class Validate } } } + return !empty($this->error) ? false : true; } /** * 根据验证规则验证数据 - * @access protected + * @access public * @param mixed $value 字段值 * @param mixed $rules 验证规则 * @return bool */ - protected function checkRule($value, $rules) + public function checkRule($value, $rules) { if ($rules instanceof \Closure) { return call_user_func_array($rules, [$value]); + } elseif ($rules instanceof ValidateRule) { + $rules = $rules->getRule(); } elseif (is_string($rules)) { $rules = explode('|', $rules); } @@ -379,21 +498,35 @@ class Validate /** * 验证单个字段规则 * @access protected - * @param string $field 字段名 - * @param mixed $value 字段值 - * @param mixed $rules 验证规则 - * @param array $data 数据 - * @param string $title 字段描述 - * @param array $msg 提示信息 + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 * @return mixed */ protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) { + if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { + // 字段已经移除 无需验证 + return true; + } + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] if (is_string($rules)) { $rules = explode('|', $rules); } - $i = 0; + + if (isset($this->append[$field])) { + // 追加额外的验证规则 + $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR); + unset($this->append[$field]); + } + + $i = 0; + $result = true; + foreach ($rules as $key => $rule) { if ($rule instanceof \Closure) { $result = call_user_func_array($rule, [$value, $data]); @@ -402,12 +535,20 @@ class Validate // 判断验证类型 list($type, $rule, $info) = $this->getValidateType($key, $rule); - // 如果不是require 有数据才会行验证 - if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { - // 验证类型 - $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { + + } elseif (array_key_exists($field, $this->remove) && (null === $this->remove[$field] || in_array($info, $this->remove[$field]))) { + // 规则已经移除 + $i++; + continue; + } + + // 验证类型 + if (isset(self::$type[$type])) { + $result = call_user_func_array(self::$type[$type], [$value, $rule, $data, $field, $title]); + } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { // 验证数据 - $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]); } else { $result = true; } @@ -415,24 +556,31 @@ class Validate if (false === $result) { // 验证失败 返回错误信息 - if (isset($msg[$i])) { + if (!empty($msg[$i])) { $message = $msg[$i]; if (is_string($message) && strpos($message, '{%') === 0) { - $message = Lang::get(substr($message, 2, -1)); + $message = facade\Lang::get(substr($message, 2, -1)); } } else { $message = $this->getRuleMsg($field, $title, $info, $rule); } + return $message; } elseif (true !== $result) { // 返回自定义错误信息 if (is_string($result) && false !== strpos($result, ':')) { - $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); + $result = str_replace(':attribute', $title, $result); + + if (strpos($result, ':rule') && is_scalar($rule)) { + $result = str_replace(':rule', (string) $rule, $result); + } } + return $result; } $i++; } + return $result; } @@ -471,14 +619,14 @@ class Validate /** * 验证是否和某个字段的值一致 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @param string $field 字段名 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 * @return bool */ - protected function confirm($value, $rule, $data, $field = '') + public function confirm($value, $rule, $data = [], $field = '') { if ('' == $rule) { if (strpos($field, '_confirm')) { @@ -487,101 +635,110 @@ class Validate $rule = $field . '_confirm'; } } + return $this->getDataValue($data, $rule) === $value; } /** * 验证是否和某个字段的值是否不同 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function different($value, $rule, $data) + public function different($value, $rule, $data = []) { return $this->getDataValue($data, $rule) != $value; } /** * 验证是否大于等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function egt($value, $rule, $data) + public function egt($value, $rule, $data = []) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value >= $val; + return $value >= $this->getDataValue($data, $rule); } /** * 验证是否大于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function gt($value, $rule, $data) + public function gt($value, $rule, $data) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value > $val; + return $value > $this->getDataValue($data, $rule); } /** * 验证是否小于等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function elt($value, $rule, $data) + public function elt($value, $rule, $data = []) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value <= $val; + return $value <= $this->getDataValue($data, $rule); } /** * 验证是否小于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function lt($value, $rule, $data) + public function lt($value, $rule, $data = []) { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value < $val; + return $value < $this->getDataValue($data, $rule); } /** * 验证是否等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function eq($value, $rule) + public function eq($value, $rule) { return $value == $rule; } /** - * 验证字段值是否为有效格式 - * @access protected - * @param mixed $value 字段值 - * @param string $rule 验证规则 - * @param array $data 验证数据 + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function is($value, $rule, $data = []) + public function must($value, $rule = null) { - switch ($rule) { + return !empty($value) || '0' == $value; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + public function is($value, $rule, $data = []) + { + switch (Loader::parseName($rule, 1, false)) { case 'require': // 必须 $result = !empty($value) || '0' == $value; @@ -594,65 +751,21 @@ class Validate // 是否是一个有效日期 $result = false !== strtotime($value); break; - case 'alpha': - // 只允许字母 - $result = $this->regex($value, '/^[A-Za-z]+$/'); - break; - case 'alphaNum': - // 只允许字母和数字 - $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); - break; - case 'alphaDash': - // 只允许字母、数字和下划线 破折号 - $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); - break; - case 'chs': - // 只允许汉字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); - break; - case 'chsAlpha': - // 只允许汉字、字母 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); - break; - case 'chsAlphaNum': - // 只允许汉字、字母和数字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); - break; - case 'chsDash': - // 只允许汉字、字母、数字和下划线_及破折号- - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); - break; case 'activeUrl': // 是否为有效的网址 $result = checkdnsrr($value); break; - case 'ip': - // 是否为IP地址 - $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); - break; - case 'url': - // 是否为一个URL地址 - $result = $this->filter($value, FILTER_VALIDATE_URL); - break; - case 'float': - // 是否为float - $result = $this->filter($value, FILTER_VALIDATE_FLOAT); - break; - case 'number': - $result = is_numeric($value); - break; - case 'integer': - // 是否为整型 - $result = $this->filter($value, FILTER_VALIDATE_INT); - break; - case 'email': - // 是否为邮箱地址 - $result = $this->filter($value, FILTER_VALIDATE_EMAIL); - break; case 'boolean': + case 'bool': // 是否为布尔值 $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; + case 'number': + $result = ctype_digit((string) $value); + break; + case 'alphaNum': + $result = ctype_alnum($value); + break; case 'array': // 是否为数组 $result = is_array($value); @@ -670,11 +783,19 @@ class Validate if (isset(self::$type[$rule])) { // 注册的验证规则 $result = call_user_func_array(self::$type[$rule], [$value]); + } elseif (function_exists('ctype_' . $rule)) { + // ctype验证规则 + $ctypeFun = 'ctype_' . $rule; + $result = $ctypeFun($value); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); } else { // 正则验证 $result = $this->regex($value, $rule); } } + return $result; } @@ -683,54 +804,56 @@ class Validate { if (function_exists('exif_imagetype')) { return exif_imagetype($image); - } else { - try { - $info = getimagesize($image); - return $info ? $info[2] : false; - } catch (\Exception $e) { - return false; - } + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; } } /** * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function activeUrl($value, $rule) + public function activeUrl($value, $rule = 'MX') { if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { $rule = 'MX'; } + return checkdnsrr($value, $rule); } /** * 验证是否有效IP - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 ipv4 ipv6 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 * @return bool */ - protected function ip($value, $rule) + public function ip($value, $rule = 'ipv4') { if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; } + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); } /** * 验证上传文件后缀 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function fileExt($file, $rule) + public function fileExt($file, $rule) { if (is_array($file)) { foreach ($file as $item) { @@ -741,19 +864,19 @@ class Validate return true; } elseif ($file instanceof File) { return $file->checkExt($rule); - } else { - return false; } + + return false; } /** * 验证上传文件类型 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function fileMime($file, $rule) + public function fileMime($file, $rule) { if (is_array($file)) { foreach ($file as $item) { @@ -764,19 +887,19 @@ class Validate return true; } elseif ($file instanceof File) { return $file->checkMime($rule); - } else { - return false; } + + return false; } /** * 验证上传文件大小 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function fileSize($file, $rule) + public function fileSize($file, $rule) { if (is_array($file)) { foreach ($file as $item) { @@ -787,64 +910,70 @@ class Validate return true; } elseif ($file instanceof File) { return $file->checkSize($rule); - } else { - return false; } + + return false; } /** * 验证图片的宽高及类型 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - protected function image($file, $rule) + public function image($file, $rule) { if (!($file instanceof File)) { return false; } + if ($rule) { - $rule = explode(',', $rule); + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { $imageType = strtolower($rule[2]); - if ('jpeg' == $imageType) { - $imageType = 'jpg'; + + if ('jpg' == $imageType) { + $imageType = 'jpeg'; } + if (image_type_to_extension($type, false) != $imageType) { return false; } } list($w, $h) = $rule; + return $w == $width && $h == $height; - } else { - return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); } + + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); } /** * 验证请求类型 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function method($value, $rule) + public function method($value, $rule) { - $method = Request::instance()->method(); + $method = Container::get('request')->method(); return strtoupper($rule) == $method; } /** * 验证时间和日期是否符合指定格式 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function dateFormat($value, $rule) + public function dateFormat($value, $rule) { $info = date_parse_from_format($rule, $value); return 0 == $info['warning_count'] && 0 == $info['error_count']; @@ -852,28 +981,30 @@ class Validate /** * 验证是否唯一 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 - * @param array $data 数据 - * @param string $field 验证字段名 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 * @return bool */ - protected function unique($value, $rule, $data, $field) + public function unique($value, $rule, $data, $field) { if (is_string($rule)) { $rule = explode(',', $rule); } + if (false !== strpos($rule[0], '\\')) { // 指定模型类 $db = new $rule[0]; } else { try { - $db = Loader::model($rule[0]); + $db = Container::get('app')->model($rule[0]); } catch (ClassNotFoundException $e) { $db = Db::name($rule[0]); } } + $key = isset($rule[1]) ? $rule[1] : $field; if (strpos($key, '^')) { @@ -881,52 +1012,55 @@ class Validate $fields = explode('^', $key); foreach ($fields as $key) { if (isset($data[$key])) { - $map[$key] = $data[$key]; + $map[] = [$key, '=', $data[$key]]; } } } elseif (strpos($key, '=')) { parse_str($key, $map); } elseif (isset($data[$field])) { - $map[$key] = $data[$field]; + $map[] = [$key, '=', $data[$field]]; } else { $map = []; } - $pk = isset($rule[3]) ? $rule[3] : $db->getPk(); + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + if (is_string($pk)) { if (isset($rule[2])) { - $map[$pk] = ['neq', $rule[2]]; + $map[] = [$pk, '<>', $rule[2]]; } elseif (isset($data[$pk])) { - $map[$pk] = ['neq', $data[$pk]]; + $map[] = [$pk, '<>', $data[$pk]]; } } + if ($db->where($map)->field($pk)->find()) { return false; } + return true; } /** * 使用行为类验证 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return mixed */ - protected function behavior($value, $rule, $data) + public function behavior($value, $rule, $data) { - return Hook::exec($rule, '', $data); + return Container::get('hook')->exec($rule, $data); } /** * 使用filter_var方式验证 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function filter($value, $rule) + public function filter($value, $rule) { if (is_string($rule) && strpos($rule, ',')) { list($rule, $param) = explode(',', $rule); @@ -936,127 +1070,133 @@ class Validate } else { $param = null; } + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); } /** * 验证某个字段等于某个值的时候必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function requireIf($value, $rule, $data) + public function requireIf($value, $rule, $data) { list($field, $val) = explode(',', $rule); + if ($this->getDataValue($data, $field) == $val) { return !empty($value) || '0' == $value; - } else { - return true; } + + return true; } /** * 通过回调方法验证某个字段是否必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function requireCallback($value, $rule, $data) + public function requireCallback($value, $rule, $data) { - $result = call_user_func_array($rule, [$value, $data]); + $result = call_user_func_array([$this, $rule], [$value, $data]); + if ($result) { return !empty($value) || '0' == $value; - } else { - return true; } + + return true; } /** * 验证某个字段有值的情况下必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function requireWith($value, $rule, $data) + public function requireWith($value, $rule, $data) { $val = $this->getDataValue($data, $rule); + if (!empty($val)) { return !empty($value) || '0' == $value; - } else { - return true; } + + return true; } /** * 验证是否在范围内 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function in($value, $rule) + public function in($value, $rule) { return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证是否不在某个范围 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function notIn($value, $rule) + public function notIn($value, $rule) { return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * between验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function between($value, $rule) + public function between($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } list($min, $max) = $rule; + return $value >= $min && $value <= $max; } /** * 使用notbetween验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function notBetween($value, $rule) + public function notBetween($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } list($min, $max) = $rule; + return $value < $min || $value > $max; } /** * 验证数据长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function length($value, $rule) + public function length($value, $rule) { if (is_array($value)) { $length = count($value); @@ -1070,20 +1210,20 @@ class Validate // 长度区间 list($min, $max) = explode(',', $rule); return $length >= $min && $length <= $max; - } else { - // 指定长度 - return $length == $rule; } + + // 指定长度 + return $length == $rule; } /** * 验证数据最大长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function max($value, $rule) + public function max($value, $rule) { if (is_array($value)) { $length = count($value); @@ -1092,17 +1232,18 @@ class Validate } else { $length = mb_strlen((string) $value); } + return $length <= $rule; } /** * 验证数据最小长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function min($value, $rule) + public function min($value, $rule) { if (is_array($value)) { $length = count($value); @@ -1111,31 +1252,32 @@ class Validate } else { $length = mb_strlen((string) $value); } + return $length >= $rule; } /** * 验证日期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function after($value, $rule, $data) + public function after($value, $rule, $data) { return strtotime($value) >= strtotime($rule); } /** * 验证日期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function before($value, $rule, $data) + public function before($value, $rule, $data) { return strtotime($value) <= strtotime($rule); } @@ -1170,17 +1312,19 @@ class Validate /** * 验证有效期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - protected function expire($value, $rule) + public function expire($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } + list($start, $end) = $rule; + if (!is_numeric($start)) { $start = strtotime($start); } @@ -1188,76 +1332,85 @@ class Validate if (!is_numeric($end)) { $end = strtotime($end); } + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; } /** * 验证IP许可 - * @access protected - * @param string $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 * @return mixed */ - protected function allowIp($value, $rule) + public function allowIp($value, $rule) { - return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证IP禁用 - * @access protected - * @param string $value 字段值 - * @param mixed $rule 验证规则 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 * @return mixed */ - protected function denyIp($value, $rule) + public function denyIp($value, $rule) { - return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 使用正则验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 正则规则或者预定义正则名 - * @return mixed + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return bool */ - protected function regex($value, $rule) + public function regex($value, $rule) { if (isset($this->regex[$rule])) { $rule = $this->regex[$rule]; + } elseif (isset($this->defaultRegex[$rule])) { + $rule = $this->defaultRegex[$rule]; } + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { // 不是正则表达式则两端补上/ $rule = '/^' . $rule . '$/'; } + return is_scalar($value) && 1 === preg_match($rule, (string) $value); } /** * 验证表单令牌 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function token($value, $rule, $data) + public function token($value, $rule, $data) { - $rule = !empty($rule) ? $rule : '__token__'; - if (!isset($data[$rule]) || !Session::has($rule)) { + $rule = !empty($rule) ? $rule : '__token__'; + $session = Container::get('session'); + + if (!isset($data[$rule]) || !$session->has($rule)) { // 令牌数据无效 return false; } // 令牌验证 - if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { + if (isset($data[$rule]) && $session->get($rule) === $data[$rule]) { // 防止重复提交 - Session::delete($rule); // 验证完成销毁session + $session->delete($rule); // 验证完成销毁session return true; } + // 开启TOKEN重置 - Session::delete($rule); + $session->delete($rule); + return false; } @@ -1270,8 +1423,8 @@ class Validate /** * 获取数据值 * @access protected - * @param array $data 数据 - * @param string $key 数据标识 支持二维 + * @param array $data 数据 + * @param string $key 数据标识 支持多维 * @return mixed */ protected function getDataValue($data, $key) @@ -1279,26 +1432,34 @@ class Validate if (is_numeric($key)) { $value = $key; } elseif (strpos($key, '.')) { - // 支持二维数组验证 - list($name1, $name2) = explode('.', $key); - $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null; + // 支持多维数组验证 + foreach (explode('.', $key) as $key) { + if (!isset($data[$key])) { + $value = null; + break; + } + $value = $data = $data[$key]; + } } else { $value = isset($data[$key]) ? $data[$key] : null; } + return $value; } /** * 获取验证规则的错误提示信息 * @access protected - * @param string $attribute 字段英文名 - * @param string $title 字段描述名 - * @param string $type 验证规则名称 - * @param mixed $rule 验证规则数据 + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 * @return string */ protected function getRuleMsg($attribute, $title, $type, $rule) { + $lang = Container::get('lang'); + if (isset($this->message[$attribute . '.' . $type])) { $msg = $this->message[$attribute . '.' . $type]; } elseif (isset($this->message[$attribute][$type])) { @@ -1310,16 +1471,20 @@ class Validate } elseif (0 === strpos($type, 'require')) { $msg = self::$typeMsg['require']; } else { - $msg = $title . Lang::get('not conform to the rules'); + $msg = $title . $lang->get('not conform to the rules'); } - if (is_string($msg) && 0 === strpos($msg, '{%')) { - $msg = Lang::get(substr($msg, 2, -1)); - } elseif (Lang::has($msg)) { - $msg = Lang::get($msg); + if (!is_string($msg)) { + return $msg; } - if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { + if (0 === strpos($msg, '{%')) { + $msg = $lang->get(substr($msg, 2, -1)); + } elseif ($lang->has($msg)) { + $msg = $lang->get($msg); + } + + if (is_scalar($rule) && false !== strpos($msg, ':')) { // 变量替换 if (is_string($rule) && strpos($rule, ',')) { $array = array_pad(explode(',', $rule), 3, ''); @@ -1327,18 +1492,22 @@ class Validate $array = array_pad([], 3, ''); } $msg = str_replace( - [':attribute', ':rule', ':1', ':2', ':3'], - [$title, (string) $rule, $array[0], $array[1], $array[2]], + [':attribute', ':1', ':2', ':3'], + [$title, $array[0], $array[1], $array[2]], $msg); + if (strpos($msg, ':rule')) { + $msg = str_replace(':rule', (string) $rule, $msg); + } } + return $msg; } /** * 获取数据验证的场景 * @access protected - * @param string $scene 验证场景 - * @return array + * @param string $scene 验证场景 + * @return void */ protected function getScene($scene = '') { @@ -1347,25 +1516,41 @@ class Validate $scene = $this->currentScene; } - if (!empty($scene) && isset($this->scene[$scene])) { + $this->only = $this->append = $this->remove = []; + + if (empty($scene)) { + return; + } + + if (method_exists($this, 'scene' . $scene)) { + call_user_func([$this, 'scene' . $scene]); + } elseif (isset($this->scene[$scene])) { // 如果设置了验证适用场景 $scene = $this->scene[$scene]; + if (is_string($scene)) { $scene = explode(',', $scene); } - } else { - $scene = []; + + $this->only = $scene; } - return $scene; } - public static function __callStatic($method, $params) + /** + * 动态方法 直接调用is方法进行验证 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) { - $class = self::make(); - if (method_exists($class, $method)) { - return call_user_func_array([$class, $method], $params); - } else { - throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method); + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); } + + array_push($args, lcfirst($method)); + + return call_user_func_array([$this, 'is'], $args); } } diff --git a/Server/thinkphp/library/think/View.php b/Server/thinkphp/library/think/View.php index ca2dadbb..284dd41a 100644 --- a/Server/thinkphp/library/think/View.php +++ b/Server/thinkphp/library/think/View.php @@ -13,80 +13,83 @@ namespace think; class View { - // 视图实例 - protected static $instance; - // 模板引擎实例 + /** + * 模板引擎实例 + * @var object + */ public $engine; - // 模板变量 - protected $data = []; - // 用于静态赋值的模板变量 - protected static $var = []; - // 视图输出替换 - protected $replace = []; /** - * 构造函数 - * @access public - * @param array $engine 模板引擎参数 - * @param array $replace 字符串替换参数 + * 模板变量 + * @var array */ - public function __construct($engine = [], $replace = []) + protected $data = []; + + /** + * 内容过滤 + * @var mixed + */ + protected $filter; + + /** + * 全局模板变量 + * @var array + */ + protected static $var = []; + + /** + * 初始化 + * @access public + * @param mixed $engine 模板引擎参数 + * @return $this + */ + public function init($engine = []) { // 初始化模板引擎 $this->engine($engine); - // 基础替换字符串 - $request = Request::instance(); - $base = $request->root(); - $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base; - if ('' != $root) { - $root = '/' . ltrim($root, '/'); - } - $baseReplace = [ - '__ROOT__' => $root, - '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()), - '__STATIC__' => $root . '/static', - '__CSS__' => $root . '/static/css', - '__JS__' => $root . '/static/js', - ]; - $this->replace = array_merge($baseReplace, (array) $replace); + + return $this; } - /** - * 初始化视图 - * @access public - * @param array $engine 模板引擎参数 - * @param array $replace 字符串替换参数 - * @return object - */ - public static function instance($engine = [], $replace = []) + public static function __make(Config $config) { - if (is_null(self::$instance)) { - self::$instance = new self($engine, $replace); - } - return self::$instance; + return (new static())->init($config->pull('template')); } /** * 模板变量静态赋值 * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 - * @return void + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this */ - public static function share($name, $value = '') + public function share($name, $value = '') { if (is_array($name)) { self::$var = array_merge(self::$var, $name); } else { self::$var[$name] = $value; } + + return $this; + } + + /** + * 清理模板变量 + * @access public + * @return void + */ + public function clear() + { + self::$var = []; + $this->data = []; } /** * 模板变量赋值 * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 + * @param mixed $name 变量名 + * @param mixed $value 变量值 * @return $this */ public function assign($name, $value = '') @@ -96,13 +99,14 @@ class View } else { $this->data[$name] = $value; } + return $this; } /** * 设置当前模板解析的引擎 * @access public - * @param array|string $options 引擎参数 + * @param array|string $options 引擎参数 * @return $this */ public function engine($options = []) @@ -114,38 +118,66 @@ class View $type = !empty($options['type']) ? $options['type'] : 'Think'; } - $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type); if (isset($options['type'])) { unset($options['type']); } - $this->engine = new $class($options); + + $this->engine = Loader::factory($type, '\\think\\view\\driver\\', $options); + return $this; } /** * 配置模板引擎 - * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 参数值 * @return $this */ public function config($name, $value = null) { $this->engine->config($name, $value); + + return $this; + } + + /** + * 检查模板是否存在 + * @access public + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->engine->exists($name); + } + + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter($filter) + { + if ($filter) { + $this->filter = $filter; + } + return $this; } /** * 解析和获取模板内容 用于输出 - * @param string $template 模板文件名或者内容 - * @param array $vars 模板输出变量 - * @param array $replace 替换内容 - * @param array $config 模板参数 - * @param bool $renderContent 是否渲染内容 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 * @return string - * @throws Exception + * @throws \Exception */ - public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) + public function fetch($template = '', $vars = [], $config = [], $renderContent = false) { // 模板变量 $vars = array_merge(self::$var, $this->data, $vars); @@ -157,9 +189,6 @@ class View // 渲染输出 try { $method = $renderContent ? 'display' : 'fetch'; - // 允许用户自定义模板的字符串替换 - $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string')); - $this->engine->config('tpl_replace_string', $replace); $this->engine->$method($template, $vars, $config); } catch (\Exception $e) { ob_end_clean(); @@ -168,47 +197,32 @@ class View // 获取并清空缓存 $content = ob_get_clean(); - // 内容过滤标签 - Hook::listen('view_filter', $content); - return $content; - } - /** - * 视图内容替换 - * @access public - * @param string|array $content 被替换内容(支持批量替换) - * @param string $replace 替换内容 - * @return $this - */ - public function replace($content, $replace = '') - { - if (is_array($content)) { - $this->replace = array_merge($this->replace, $content); - } else { - $this->replace[$content] = $replace; + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); } - return $this; + + return $content; } /** * 渲染内容输出 * @access public - * @param string $content 内容 - * @param array $vars 模板输出变量 - * @param array $replace 替换内容 - * @param array $config 模板参数 + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 * @return mixed */ - public function display($content, $vars = [], $replace = [], $config = []) + public function display($content, $vars = [], $config = []) { - return $this->fetch($content, $vars, $replace, $config, true); + return $this->fetch($content, $vars, $config, true); } /** * 模板变量赋值 * @access public - * @param string $name 变量名 - * @param mixed $value 变量值 + * @param string $name 变量名 + * @param mixed $value 变量值 */ public function __set($name, $value) { @@ -218,7 +232,7 @@ class View /** * 取得模板显示变量的值 * @access protected - * @param string $name 模板变量 + * @param string $name 模板变量 * @return mixed */ public function __get($name) @@ -229,7 +243,7 @@ class View /** * 检测模板变量是否设置 * @access public - * @param string $name 模板变量名 + * @param string $name 模板变量名 * @return bool */ public function __isset($name) diff --git a/Server/thinkphp/library/think/cache/Driver.php b/Server/thinkphp/library/think/cache/Driver.php index 07805e48..64216810 100644 --- a/Server/thinkphp/library/think/cache/Driver.php +++ b/Server/thinkphp/library/think/cache/Driver.php @@ -11,19 +11,53 @@ namespace think\cache; +use think\Container; + /** * 缓存基础类 */ abstract class Driver { + /** + * 驱动句柄 + * @var object + */ protected $handler = null; + + /** + * 缓存读取次数 + * @var integer + */ + protected $readTimes = 0; + + /** + * 缓存写入次数 + * @var integer + */ + protected $writeTimes = 0; + + /** + * 缓存参数 + * @var array + */ protected $options = []; + + /** + * 缓存标签 + * @var string + */ protected $tag; + /** + * 序列化方法 + * @var array + */ + protected static $serialize = ['serialize', 'unserialize', 'think_serialize:', 16]; + /** * 判断缓存是否存在 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ abstract public function has($name); @@ -31,8 +65,8 @@ abstract class Driver /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ abstract public function get($name, $default = false); @@ -40,9 +74,9 @@ abstract class Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 * @return boolean */ abstract public function set($name, $value, $expire = null); @@ -50,8 +84,8 @@ abstract class Driver /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ abstract public function inc($name, $step = 1); @@ -59,8 +93,8 @@ abstract class Driver /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ abstract public function dec($name, $step = 1); @@ -68,7 +102,7 @@ abstract class Driver /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ abstract public function rm($name); @@ -76,15 +110,30 @@ abstract class Driver /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ abstract public function clear($tag = null); + /** + * 获取有效期 + * @access protected + * @param integer|\DateTime $expire 有效期 + * @return integer + */ + protected function getExpireTime($expire) + { + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + + return $expire; + } + /** * 获取实际的缓存标识 - * @access public - * @param string $name 缓存名 + * @access protected + * @param string $name 缓存名 * @return string */ protected function getCacheKey($name) @@ -95,12 +144,13 @@ abstract class Driver /** * 读取缓存并删除 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return mixed */ public function pull($name) { $result = $this->get($name, false); + if ($result) { $this->rm($name); return $result; @@ -112,9 +162,9 @@ abstract class Driver /** * 如果不存在则写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 * @return mixed */ public function remember($name, $value, $expire = null) @@ -129,14 +179,18 @@ abstract class Driver try { // 锁定 $this->set($name . '_lock', true); + if ($value instanceof \Closure) { - $value = call_user_func($value); + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); } + + // 缓存数据 $this->set($name, $value, $expire); + // 解锁 $this->rm($name . '_lock'); } catch (\Exception $e) { - // 解锁 $this->rm($name . '_lock'); throw $e; } catch (\throwable $e) { @@ -146,15 +200,16 @@ abstract class Driver } else { $value = $this->get($name); } + return $value; } /** * 缓存标签 * @access public - * @param string $name 标签名 - * @param string|array $keys 缓存标识 - * @param bool $overlay 是否覆盖 + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 * @return $this */ public function tag($name, $keys = null, $overlay = false) @@ -164,53 +219,66 @@ abstract class Driver } elseif (is_null($keys)) { $this->tag = $name; } else { - $key = 'tag_' . md5($name); + $key = $this->getTagkey($name); + if (is_string($keys)) { $keys = explode(',', $keys); } + $keys = array_map([$this, 'getCacheKey'], $keys); + if ($overlay) { $value = $keys; } else { $value = array_unique(array_merge($this->getTagItem($name), $keys)); } + $this->set($key, implode(',', $value), 0); } + return $this; } /** * 更新标签 - * @access public - * @param string $name 缓存标识 + * @access protected + * @param string $name 缓存标识 * @return void */ protected function setTagItem($name) { if ($this->tag) { - $key = 'tag_' . md5($this->tag); + $key = $this->getTagkey($this->tag); $this->tag = null; + if ($this->has($key)) { $value = explode(',', $this->get($key)); $value[] = $name; - $value = implode(',', array_unique($value)); + + if (count($value) > 1000) { + array_shift($value); + } + + $value = implode(',', array_unique($value)); } else { $value = $name; } + $this->set($key, $value, 0); } } /** * 获取标签包含的缓存标识 - * @access public - * @param string $tag 缓存标签 + * @access protected + * @param string $tag 缓存标签 * @return array */ protected function getTagItem($tag) { - $key = 'tag_' . md5($tag); + $key = $this->getTagkey($tag); $value = $this->get($key); + if ($value) { return array_filter(explode(',', $value)); } else { @@ -218,6 +286,58 @@ abstract class Driver } } + protected function getTagKey($tag) + { + return 'tag_' . md5($tag); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data + * @return string + */ + protected function serialize($data) + { + if (is_scalar($data) || !$this->options['serialize']) { + return $data; + } + + $serialize = self::$serialize[0]; + + return self::$serialize[2] . $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data + * @return mixed + */ + protected function unserialize($data) + { + if ($this->options['serialize'] && 0 === strpos($data, self::$serialize[2])) { + $unserialize = self::$serialize[1]; + + return $unserialize(substr($data, self::$serialize[3])); + } else { + return $data; + } + } + + /** + * 注册序列化机制 + * @access public + * @param callable $serialize 序列化方法 + * @param callable $unserialize 反序列化方法 + * @param string $prefix 序列化前缀标识 + * @return $this + */ + public static function registerSerialize($serialize, $unserialize, $prefix = 'think_serialize:') + { + self::$serialize = [$serialize, $unserialize, $prefix, strlen($prefix)]; + } + /** * 返回句柄对象,可执行其它高级方法 * @@ -228,4 +348,19 @@ abstract class Driver { return $this->handler; } + + public function getReadTimes() + { + return $this->readTimes; + } + + public function getWriteTimes() + { + return $this->writeTimes; + } + + public function __call($method, $args) + { + return call_user_func_array([$this->handler, $method], $args); + } } diff --git a/Server/thinkphp/library/think/cache/driver/File.php b/Server/thinkphp/library/think/cache/driver/File.php index fee64894..60be08db 100644 --- a/Server/thinkphp/library/think/cache/driver/File.php +++ b/Server/thinkphp/library/think/cache/driver/File.php @@ -12,6 +12,7 @@ namespace think\cache\driver; use think\cache\Driver; +use think\Container; /** * 文件类型缓存类 @@ -23,14 +24,16 @@ class File extends Driver 'expire' => 0, 'cache_subdir' => true, 'prefix' => '', - 'path' => CACHE_PATH, + 'path' => '', + 'hash_type' => 'md5', 'data_compress' => false, + 'serialize' => true, ]; protected $expire; /** - * 构造函数 + * 架构函数 * @param array $options */ public function __construct($options = []) @@ -38,9 +41,13 @@ class File extends Driver if (!empty($options)) { $this->options = array_merge($this->options, $options); } - if (substr($this->options['path'], -1) != DS) { - $this->options['path'] .= DS; + + if (empty($this->options['path'])) { + $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache' . DIRECTORY_SEPARATOR; + } elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; } + $this->init(); } @@ -52,11 +59,13 @@ class File extends Driver private function init() { // 创建项目缓存目录 - if (!is_dir($this->options['path'])) { - if (mkdir($this->options['path'], 0755, true)) { + try { + if (!is_dir($this->options['path']) && mkdir($this->options['path'], 0755, true)) { return true; } + } catch (\Exception $e) { } + return false; } @@ -69,62 +78,77 @@ class File extends Driver */ protected function getCacheKey($name, $auto = false) { - $name = md5($name); + $name = hash($this->options['hash_type'], $name); + if ($this->options['cache_subdir']) { // 使用子目录 - $name = substr($name, 0, 2) . DS . substr($name, 2); + $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); } + if ($this->options['prefix']) { - $name = $this->options['prefix'] . DS . $name; + $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name; } + $filename = $this->options['path'] . $name . '.php'; $dir = dirname($filename); if ($auto && !is_dir($dir)) { - mkdir($dir, 0755, true); + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + } } + return $filename; } /** * 判断缓存是否存在 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { - return $this->get($name) ? true : false; + return false !== $this->get($name) ? true : false; } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $filename = $this->getCacheKey($name); + if (!is_file($filename)) { return $default; } + $content = file_get_contents($filename); $this->expire = null; + if (false !== $content) { $expire = (int) substr($content, 8, 12); if (0 != $expire && time() > filemtime($filename) + $expire) { + //缓存过期删除缓存文件 + $this->unlink($filename); return $default; } + $this->expire = $expire; $content = substr($content, 32); + if ($this->options['data_compress'] && function_exists('gzcompress')) { //启用数据压缩 $content = gzuncompress($content); } - $content = unserialize($content); - return $content; + return $this->unserialize($content); } else { return $default; } @@ -133,30 +157,36 @@ class File extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + + $expire = $this->getExpireTime($expire); $filename = $this->getCacheKey($name, true); + if ($this->tag && !is_file($filename)) { $first = true; } - $data = serialize($value); + + $data = $this->serialize($value); + if ($this->options['data_compress'] && function_exists('gzcompress')) { //数据压缩 $data = gzcompress($data, 3); } + $data = "\n" . $data; $result = file_put_contents($filename, $data); + if ($result) { isset($first) && $this->setTagItem($filename); clearstatcache(); @@ -169,8 +199,8 @@ class File extends Driver /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) @@ -189,8 +219,8 @@ class File extends Driver /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) @@ -209,14 +239,15 @@ class File extends Driver /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { - $filename = $this->getCacheKey($name); + $this->writeTimes++; + try { - return $this->unlink($filename); + return $this->unlink($this->getCacheKey($name)); } catch (\Exception $e) { } } @@ -224,7 +255,7 @@ class File extends Driver /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -235,27 +266,35 @@ class File extends Driver foreach ($keys as $key) { $this->unlink($key); } - $this->rm('tag_' . md5($tag)); + $this->rm($this->getTagKey($tag)); return true; } - $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); + + $this->writeTimes++; + + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*'); + foreach ($files as $path) { if (is_dir($path)) { - $matches = glob($path . '/*.php'); + $matches = glob($path . DIRECTORY_SEPARATOR . '*.php'); if (is_array($matches)) { - array_map('unlink', $matches); + array_map(function ($v) { + $this->unlink($v); + }, $matches); } rmdir($path); } else { - unlink($path); + $this->unlink($path); } } + return true; } /** * 判断文件是否存在后,删除 - * @param $path + * @access private + * @param string $path * @return bool * @author byron sampson * @return boolean diff --git a/Server/thinkphp/library/think/cache/driver/Lite.php b/Server/thinkphp/library/think/cache/driver/Lite.php index 8cbf08f9..0cfe3907 100644 --- a/Server/thinkphp/library/think/cache/driver/Lite.php +++ b/Server/thinkphp/library/think/cache/driver/Lite.php @@ -26,18 +26,19 @@ class Lite extends Driver ]; /** - * 构造函数 + * 架构函数 * @access public * - * @param array $options + * @param array $options */ public function __construct($options = []) { if (!empty($options)) { $this->options = array_merge($this->options, $options); } - if (substr($this->options['path'], -1) != DS) { - $this->options['path'] .= DS; + + if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; } } @@ -45,7 +46,7 @@ class Lite extends Driver /** * 取得变量的存储文件名 * @access protected - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return string */ protected function getCacheKey($name) @@ -56,7 +57,7 @@ class Lite extends Driver /** * 判断缓存是否存在 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return mixed */ public function has($name) @@ -67,21 +68,26 @@ class Lite extends Driver /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $filename = $this->getCacheKey($name); + if (is_file($filename)) { // 判断是否过期 $mtime = filemtime($filename); + if ($mtime < time()) { // 清除已经过期的文件 unlink($filename); return $default; } + return include $filename; } else { return $default; @@ -90,41 +96,49 @@ class Lite extends Driver /** * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 * @return bool */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { $expire = $expire->getTimestamp(); } else { $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; $expire = time() + $expire; } + $filename = $this->getCacheKey($name); + if ($this->tag && !is_file($filename)) { $first = true; } + $ret = file_put_contents($filename, ("setTagItem($filename); touch($filename, $expire); } + return $ret; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) @@ -134,14 +148,15 @@ class Lite extends Driver } else { $value = $step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) @@ -151,24 +166,27 @@ class Lite extends Driver } else { $value = -$step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + return unlink($this->getCacheKey($name)); } /** * 清除缓存 - * @access public - * @param string $tag 标签名 + * @access public + * @param string $tag 标签名 * @return bool */ public function clear($tag = null) @@ -179,9 +197,13 @@ class Lite extends Driver foreach ($keys as $key) { unlink($key); } - $this->rm('tag_' . md5($tag)); + + $this->rm($this->getTagKey($tag)); return true; } - array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); + + $this->writeTimes++; + + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*.php')); } } diff --git a/Server/thinkphp/library/think/cache/driver/Memcache.php b/Server/thinkphp/library/think/cache/driver/Memcache.php index 58703ea3..1c535597 100644 --- a/Server/thinkphp/library/think/cache/driver/Memcache.php +++ b/Server/thinkphp/library/think/cache/driver/Memcache.php @@ -22,12 +22,13 @@ class Memcache extends Driver 'timeout' => 0, // 超时时间(单位:毫秒) 'persistent' => true, 'prefix' => '', + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 * @throws \BadFunctionCallException */ public function __construct($options = []) @@ -35,16 +36,21 @@ class Memcache extends Driver if (!extension_loaded('memcache')) { throw new \BadFunctionCallException('not support: memcache'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } + $this->handler = new \Memcache; + // 支持集群 $hosts = explode(',', $this->options['host']); $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 foreach ((array) $hosts as $i => $host) { $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; @@ -57,99 +63,115 @@ class Memcache extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { $key = $this->getCacheKey($name); + return false !== $this->handler->get($key); } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $result = $this->handler->get($this->getCacheKey($name)); - return false !== $result ? $result : $default; + + return false !== $result ? $this->unserialize($result) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateTime $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } - $key = $this->getCacheKey($name); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if ($this->handler->set($key, $value, 0, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { return $this->handler->increment($key, $step); } + return $this->handler->set($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); $value = $this->handler->get($key) - $step; $res = $this->handler->set($key, $value); - if (!$res) { - return false; - } else { - return $value; - } + + return !$res ? false : $value; } /** * 删除缓存 - * @param string $name 缓存变量名 - * @param bool|false $ttl + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl * @return bool */ public function rm($name, $ttl = false) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return false === $ttl ? $this->handler->delete($key) : $this->handler->delete($key, $ttl); @@ -158,7 +180,7 @@ class Memcache extends Driver /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return bool */ public function clear($tag = null) @@ -166,12 +188,19 @@ class Memcache extends Driver if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); + foreach ($keys as $key) { $this->handler->delete($key); } - $this->rm('tag_' . md5($tag)); + + $tagName = $this->getTagKey($tag); + $this->rm($tagName); return true; } + + $this->writeTimes++; + return $this->handler->flush(); } + } diff --git a/Server/thinkphp/library/think/cache/driver/Memcached.php b/Server/thinkphp/library/think/cache/driver/Memcached.php index 5aab5a30..4533e78a 100644 --- a/Server/thinkphp/library/think/cache/driver/Memcached.php +++ b/Server/thinkphp/library/think/cache/driver/Memcached.php @@ -16,49 +16,58 @@ use think\cache\Driver; class Memcached extends Driver { protected $options = [ - 'host' => '127.0.0.1', - 'port' => 11211, - 'expire' => 0, - 'timeout' => 0, // 超时时间(单位:毫秒) - 'prefix' => '', - 'username' => '', //账号 - 'password' => '', //密码 - 'option' => [], + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 */ public function __construct($options = []) { if (!extension_loaded('memcached')) { throw new \BadFunctionCallException('not support: memcached'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } + $this->handler = new \Memcached; + if (!empty($this->options['option'])) { $this->handler->setOptions($this->options['option']); } + // 设置连接超时时间(单位:毫秒) if ($this->options['timeout'] > 0) { $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); } + // 支持集群 $hosts = explode(',', $this->options['host']); $ports = explode(',', $this->options['port']); if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 $servers = []; foreach ((array) $hosts as $i => $host) { $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; } + $this->handler->addServers($servers); + $this->handler->setOption(\Memcached::OPT_COMPRESSION, false); if ('' != $this->options['username']) { $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); @@ -68,100 +77,115 @@ class Memcached extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { $key = $this->getCacheKey($name); + return $this->handler->get($key) ? true : false; } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $result = $this->handler->get($this->getCacheKey($name)); - return false !== $result ? $result : $default; + + return false !== $result ? $this->unserialize($result) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } + $key = $this->getCacheKey($name); - $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if ($this->handler->set($key, $value, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { return $this->handler->increment($key, $step); } + return $this->handler->set($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); $value = $this->handler->get($key) - $step; $res = $this->handler->set($key, $value); - if (!$res) { - return false; - } else { - return $value; - } + + return !$res ? false : $value; } /** * 删除缓存 - * @param string $name 缓存变量名 - * @param bool|false $ttl + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl * @return bool */ public function rm($name, $ttl = false) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return false === $ttl ? $this->handler->delete($key) : $this->handler->delete($key, $ttl); @@ -170,7 +194,7 @@ class Memcached extends Driver /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return bool */ public function clear($tag = null) @@ -178,10 +202,78 @@ class Memcached extends Driver if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); + $this->handler->deleteMulti($keys); - $this->rm('tag_' . md5($tag)); + $this->rm($this->getTagKey($tag)); + return true; } + + $this->writeTimes++; + return $this->handler->flush(); } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($keys)) { + $this->tag = $name; + } else { + $tagName = $this->getTagKey($name); + if ($overlay) { + $this->handler->delete($tagName); + } + + if (!$this->has($tagName)) { + $this->handler->set($tagName, ''); + } + + foreach ($keys as $key) { + $this->handler->append($tagName, ',' . $key); + } + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $tagName = $this->getTagKey($this->tag); + + if ($this->has($tagName)) { + $this->handler->append($tagName, ',' . $name); + } else { + $this->handler->set($tagName, $name); + } + + $this->tag = null; + } + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + public function getTagItem($tag) + { + $tagName = $this->getTagKey($tag); + return explode(',', trim($this->handler->get($tagName), ',')); + } } diff --git a/Server/thinkphp/library/think/cache/driver/Redis.php b/Server/thinkphp/library/think/cache/driver/Redis.php index 8956e774..4eff2cf5 100644 --- a/Server/thinkphp/library/think/cache/driver/Redis.php +++ b/Server/thinkphp/library/think/cache/driver/Redis.php @@ -31,98 +31,121 @@ class Redis extends Driver 'expire' => 0, 'persistent' => false, 'prefix' => '', + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 */ public function __construct($options = []) { - if (!extension_loaded('redis')) { - throw new \BadFunctionCallException('not support: redis'); - } if (!empty($options)) { $this->options = array_merge($this->options, $options); } - $this->handler = new \Redis; - if ($this->options['persistent']) { - $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->options as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) { + $params[$key] = $val; + unset($this->options[$key]); + } + } + + if ('' == $this->options['password']) { + unset($this->options['password']); + } + + $this->handler = new \Predis\Client($this->options, $params); + + $this->options['prefix'] = ''; } else { - $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); - } - - if ('' != $this->options['password']) { - $this->handler->auth($this->options['password']); - } - - if (0 != $this->options['select']) { - $this->handler->select($this->options['select']); + throw new \BadFunctionCallException('not support: redis'); } } /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { - return (bool) $this->handler->exists($this->getCacheKey($name)); + return $this->handler->exists($this->getCacheKey($name)) ? true : false; } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $value = $this->handler->get($this->getCacheKey($name)); + if (is_null($value) || false === $value) { return $default; } - try { - $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value; - } catch (\Exception $e) { - $result = $default; - } - - return $result; + return $this->unserialize($value); } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } - $key = $this->getCacheKey($name); - $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + + $value = $this->serialize($value); + if ($expire) { $result = $this->handler->setex($key, $expire, $value); } else { $result = $this->handler->set($key, $value); } + isset($first) && $this->setTagItem($key); + return $result; } @@ -135,6 +158,8 @@ class Redis extends Driver */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); return $this->handler->incrby($key, $step); @@ -149,6 +174,8 @@ class Redis extends Driver */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); return $this->handler->decrby($key, $step); @@ -157,18 +184,20 @@ class Redis extends Driver /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { - return $this->handler->delete($this->getCacheKey($name)); + $this->writeTimes++; + + return $this->handler->del($this->getCacheKey($name)); } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -176,13 +205,68 @@ class Redis extends Driver if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - $this->handler->delete($key); - } - $this->rm('tag_' . md5($tag)); + + $this->handler->del($keys); + + $tagName = $this->getTagKey($tag); + $this->handler->del($tagName); return true; } + + $this->writeTimes++; + return $this->handler->flushDB(); } + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($keys)) { + $this->tag = $name; + } else { + $tagName = $this->getTagKey($name); + if ($overlay) { + $this->handler->del($tagName); + } + + foreach ($keys as $key) { + $this->handler->sAdd($tagName, $key); + } + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $tagName = $this->getTagKey($this->tag); + $this->handler->sAdd($tagName, $name); + } + } + + /** + * 获取标签包含的缓存标识 + * @access protected + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $tagName = $this->getTagKey($tag); + return $this->handler->sMembers($tagName); + } } diff --git a/Server/thinkphp/library/think/cache/driver/Sqlite.php b/Server/thinkphp/library/think/cache/driver/Sqlite.php index dc2ee055..f57361e3 100644 --- a/Server/thinkphp/library/think/cache/driver/Sqlite.php +++ b/Server/thinkphp/library/think/cache/driver/Sqlite.php @@ -25,30 +25,34 @@ class Sqlite extends Driver 'prefix' => '', 'expire' => 0, 'persistent' => false, + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 - * @throws \BadFunctionCallException + * 架构函数 * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException */ public function __construct($options = []) { if (!extension_loaded('sqlite')) { throw new \BadFunctionCallException('not support: sqlite'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } - $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); } /** * 获取实际的缓存标识 * @access public - * @param string $name 缓存名 + * @param string $name 缓存名 * @return string */ protected function getCacheKey($name) @@ -59,82 +63,101 @@ class Sqlite extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { - $name = $this->getCacheKey($name); - $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; $result = sqlite_query($this->handler, $sql); + return sqlite_num_rows($result); } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { - $name = $this->getCacheKey($name); - $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $this->readTimes++; + + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { $content = sqlite_fetch_single($result); if (function_exists('gzcompress')) { //启用数据压缩 $content = gzuncompress($content); } - return unserialize($content); + + return $this->unserialize($content); } + return $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { - $name = $this->getCacheKey($name); - $value = sqlite_escape_string(serialize($value)); + $this->writeTimes++; + + $name = $this->getCacheKey($name); + + $value = sqlite_escape_string($this->serialize($value)); + if (is_null($expire)) { $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { $expire = $expire->getTimestamp(); } else { $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 } + if (function_exists('gzcompress')) { //数据压缩 $value = gzcompress($value, 3); } + if ($this->tag) { $tag = $this->tag; $this->tag = null; } else { $tag = ''; } + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + if (sqlite_query($this->handler, $sql)) { return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) @@ -144,14 +167,15 @@ class Sqlite extends Driver } else { $value = $step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) @@ -161,39 +185,49 @@ class Sqlite extends Driver } else { $value = -$step; } + return $this->set($name, $value, 0) ? $value : false; } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + $name = $this->getCacheKey($name); - $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; sqlite_query($this->handler, $sql); + return true; } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) { if ($tag) { - $name = sqlite_escape_string($tag); + $name = sqlite_escape_string($this->getTagKey($tag)); $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; sqlite_query($this->handler, $sql); return true; } + + $this->writeTimes++; + $sql = 'DELETE FROM ' . $this->options['table']; + sqlite_query($this->handler, $sql); + return true; } } diff --git a/Server/thinkphp/library/think/cache/driver/Wincache.php b/Server/thinkphp/library/think/cache/driver/Wincache.php index 03f8d357..ef157841 100644 --- a/Server/thinkphp/library/think/cache/driver/Wincache.php +++ b/Server/thinkphp/library/think/cache/driver/Wincache.php @@ -20,21 +20,23 @@ use think\cache\Driver; class Wincache extends Driver { protected $options = [ - 'prefix' => '', - 'expire' => 0, + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 - * @throws \BadFunctionCallException + * 架构函数 * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException */ public function __construct($options = []) { if (!function_exists('wincache_ucache_info')) { throw new \BadFunctionCallException('not support: WinCache'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } @@ -43,110 +45,131 @@ class Wincache extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { + $this->readTimes++; + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key); } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $key = $this->getCacheKey($name); - return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; + + return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - $key = $this->getCacheKey($name); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if ($this->tag && !$this->has($name)) { $first = true; } + if (wincache_ucache_set($key, $value, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return wincache_ucache_inc($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return wincache_ucache_dec($key, $step); } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + return wincache_ucache_delete($this->getCacheKey($name)); } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) { if ($tag) { $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - wincache_ucache_delete($key); - } - $this->rm('tag_' . md5($tag)); + + wincache_ucache_delete($keys); + + $tagName = $this->getTagkey($tag); + $this->rm($tagName); return true; - } else { - return wincache_ucache_clear(); } + + $this->writeTimes++; + return wincache_ucache_clear(); } } diff --git a/Server/thinkphp/library/think/cache/driver/Xcache.php b/Server/thinkphp/library/think/cache/driver/Xcache.php index 4d94c033..4e698597 100644 --- a/Server/thinkphp/library/think/cache/driver/Xcache.php +++ b/Server/thinkphp/library/think/cache/driver/Xcache.php @@ -20,14 +20,15 @@ use think\cache\Driver; class Xcache extends Driver { protected $options = [ - 'prefix' => '', - 'expire' => 0, + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, ]; /** - * 构造函数 - * @param array $options 缓存参数 + * 架构函数 * @access public + * @param array $options 缓存参数 * @throws \BadFunctionCallException */ public function __construct($options = []) @@ -35,6 +36,7 @@ class Xcache extends Driver if (!function_exists('xcache_info')) { throw new \BadFunctionCallException('not support: Xcache'); } + if (!empty($options)) { $this->options = array_merge($this->options, $options); } @@ -43,96 +45,113 @@ class Xcache extends Driver /** * 判断缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return bool */ public function has($name) { $key = $this->getCacheKey($name); + return xcache_isset($key); } /** * 读取缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 + * @param string $name 缓存变量名 + * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { + $this->readTimes++; + $key = $this->getCacheKey($name); - return xcache_isset($key) ? xcache_get($key) : $default; + + return xcache_isset($key) ? $this->unserialize(xcache_get($key)) : $default; } /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) { + $this->writeTimes++; + if (is_null($expire)) { $expire = $this->options['expire']; } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } + if ($this->tag && !$this->has($name)) { $first = true; } - $key = $this->getCacheKey($name); + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + if (xcache_set($key, $value, $expire)) { isset($first) && $this->setTagItem($key); return true; } + return false; } /** * 自增缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function inc($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return xcache_inc($key, $step); } /** * 自减缓存(针对数值缓存) * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 + * @param string $name 缓存变量名 + * @param int $step 步长 * @return false|int */ public function dec($name, $step = 1) { + $this->writeTimes++; + $key = $this->getCacheKey($name); + return xcache_dec($key, $step); } /** * 删除缓存 * @access public - * @param string $name 缓存变量名 + * @param string $name 缓存变量名 * @return boolean */ public function rm($name) { + $this->writeTimes++; + return xcache_unset($this->getCacheKey($name)); } /** * 清除缓存 * @access public - * @param string $tag 标签名 + * @param string $tag 标签名 * @return boolean */ public function clear($tag = null) @@ -140,12 +159,17 @@ class Xcache extends Driver if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); + foreach ($keys as $key) { xcache_unset($key); } - $this->rm('tag_' . md5($tag)); + + $this->rm($this->getTagKey($tag)); return true; } + + $this->writeTimes++; + if (function_exists('xcache_unset_by_prefix')) { return xcache_unset_by_prefix($this->options['prefix']); } else { diff --git a/Server/thinkphp/library/think/config/driver/Ini.php b/Server/thinkphp/library/think/config/driver/Ini.php index bcd12b69..b2a647d1 100644 --- a/Server/thinkphp/library/think/config/driver/Ini.php +++ b/Server/thinkphp/library/think/config/driver/Ini.php @@ -13,12 +13,19 @@ namespace think\config\driver; class Ini { - public function parse($config) + protected $config; + + public function __construct($config) { - if (is_file($config)) { - return parse_ini_file($config, true); + $this->config = $config; + } + + public function parse() + { + if (is_file($this->config)) { + return parse_ini_file($this->config, true); } else { - return parse_ini_string($config, true); + return parse_ini_string($this->config, true); } } } diff --git a/Server/thinkphp/library/think/config/driver/Json.php b/Server/thinkphp/library/think/config/driver/Json.php index 479dcc89..0d77c8ed 100644 --- a/Server/thinkphp/library/think/config/driver/Json.php +++ b/Server/thinkphp/library/think/config/driver/Json.php @@ -13,12 +13,19 @@ namespace think\config\driver; class Json { - public function parse($config) + protected $config; + + public function __construct($config) { if (is_file($config)) { $config = file_get_contents($config); } - $result = json_decode($config, true); - return $result; + + $this->config = $config; + } + + public function parse() + { + return json_decode($this->config, true); } } diff --git a/Server/thinkphp/library/think/config/driver/Xml.php b/Server/thinkphp/library/think/config/driver/Xml.php index 1158519f..9d696338 100644 --- a/Server/thinkphp/library/think/config/driver/Xml.php +++ b/Server/thinkphp/library/think/config/driver/Xml.php @@ -13,19 +13,28 @@ namespace think\config\driver; class Xml { - public function parse($config) + protected $config; + + public function __construct($config) { - if (is_file($config)) { - $content = simplexml_load_file($config); + $this->config = $config; + } + + public function parse() + { + if (is_file($this->config)) { + $content = simplexml_load_file($this->config); } else { - $content = simplexml_load_string($config); + $content = simplexml_load_string($this->config); } + $result = (array) $content; foreach ($result as $key => $val) { if (is_object($val)) { $result[$key] = (array) $val; } } + return $result; } } diff --git a/Server/thinkphp/library/think/console/Command.php b/Server/thinkphp/library/think/console/Command.php index d0caad2f..a208e7b5 100644 --- a/Server/thinkphp/library/think/console/Command.php +++ b/Server/thinkphp/library/think/console/Command.php @@ -467,4 +467,16 @@ class Command throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } + + /** + * 输出表格 + * @param Table $table + * @return string + */ + protected function table(Table $table) + { + $content = $table->render(); + $this->output->writeln($content); + return $content; + } } diff --git a/Server/thinkphp/library/think/console/command/Build.php b/Server/thinkphp/library/think/console/command/Build.php index 39806c3f..88a5bf82 100644 --- a/Server/thinkphp/library/think/console/command/Build.php +++ b/Server/thinkphp/library/think/console/command/Build.php @@ -15,10 +15,11 @@ use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; +use think\facade\App; +use think\facade\Build as AppBuild; class Build extends Command { - /** * {@inheritdoc} */ @@ -35,7 +36,7 @@ class Build extends Command protected function execute(Input $input, Output $output) { if ($input->hasOption('module')) { - \think\Build::module($input->getOption('module')); + AppBuild::module($input->getOption('module')); $output->writeln("Successed"); return; } @@ -43,13 +44,15 @@ class Build extends Command if ($input->hasOption('config')) { $build = include $input->getOption('config'); } else { - $build = include APP_PATH . 'build.php'; + $build = include App::getAppPath() . 'build.php'; } + if (empty($build)) { $output->writeln("Build Config Is Empty"); return; } - \think\Build::run($build); + + AppBuild::run($build); $output->writeln("Successed"); } diff --git a/Server/thinkphp/library/think/console/command/Clear.php b/Server/thinkphp/library/think/console/command/Clear.php index 1b5102ec..14425759 100644 --- a/Server/thinkphp/library/think/console/command/Clear.php +++ b/Server/thinkphp/library/think/console/command/Clear.php @@ -10,12 +10,12 @@ // +---------------------------------------------------------------------- namespace think\console\command; -use think\Cache; use think\console\Command; use think\console\Input; -use think\console\input\Argument; use think\console\input\Option; use think\console\Output; +use think\facade\App; +use think\facade\Cache; class Clear extends Command { @@ -24,39 +24,46 @@ class Clear extends Command // 指令配置 $this ->setName('clear') - ->addArgument('type', Argument::OPTIONAL, 'type to clear', null) ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('route', 'u', Option::VALUE_NONE, 'clear route cache') + ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') + ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') ->setDescription('Clear runtime file'); } protected function execute(Input $input, Output $output) { - $path = $input->getOption('path') ?: RUNTIME_PATH; - - $type = $input->getArgument('type'); - - if ($type == 'route') { - Cache::clear('route_check'); + if ($input->getOption('route')) { + Cache::clear('route_cache'); } else { - if (is_dir($path)) { - $this->clearPath($path); + if ($input->getOption('cache')) { + $path = App::getRuntimePath() . 'cache'; + } elseif ($input->getOption('log')) { + $path = App::getRuntimePath() . 'log'; + } else { + $path = $input->getOption('path') ?: App::getRuntimePath(); } + + $rmdir = $input->getOption('dir') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); } $output->writeln("Clear Successed"); } - protected function clearPath($path) + protected function clear($path, $rmdir) { - $path = realpath($path) . DS; - $files = scandir($path); - if ($files) { - foreach ($files as $file) { - if ('.' != $file && '..' != $file && is_dir($path . $file)) { - $this->clearPath($path . $file); - } elseif ('.gitignore' != $file && is_file($path . $file)) { - unlink($path . $file); + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); + if ($rmdir) { + rmdir($path . $file); } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); } } } diff --git a/Server/thinkphp/library/think/console/command/Help.php b/Server/thinkphp/library/think/console/command/Help.php index bae2c653..f1b63b42 100644 --- a/Server/thinkphp/library/think/console/command/Help.php +++ b/Server/thinkphp/library/think/console/command/Help.php @@ -19,7 +19,6 @@ use think\console\Output; class Help extends Command { - private $command; /** diff --git a/Server/thinkphp/library/think/console/command/Lists.php b/Server/thinkphp/library/think/console/command/Lists.php index 084ddaa2..6eb856c2 100644 --- a/Server/thinkphp/library/think/console/command/Lists.php +++ b/Server/thinkphp/library/think/console/command/Lists.php @@ -13,14 +13,13 @@ namespace think\console\command; use think\console\Command; use think\console\Input; -use think\console\Output; use think\console\input\Argument as InputArgument; -use think\console\input\Option as InputOption; use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; class Lists extends Command { - /** * {@inheritdoc} */ @@ -68,7 +67,7 @@ EOF { return new InputDefinition([ new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), - new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list') + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), ]); } } diff --git a/Server/thinkphp/library/think/console/command/Make.php b/Server/thinkphp/library/think/console/command/Make.php index d1daf34f..2f20954a 100644 --- a/Server/thinkphp/library/think/console/command/Make.php +++ b/Server/thinkphp/library/think/console/command/Make.php @@ -11,16 +11,16 @@ namespace think\console\command; -use think\App; -use think\Config; use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\Output; +use think\facade\App; +use think\facade\Config; +use think\facade\Env; abstract class Make extends Command { - protected $type; abstract protected function getStub(); @@ -45,7 +45,7 @@ abstract class Make extends Command } if (!is_dir(dirname($pathname))) { - mkdir(strtolower(dirname($pathname)), 0755, true); + mkdir(dirname($pathname), 0755, true); } file_put_contents($pathname, $this->buildClass($classname)); @@ -62,26 +62,26 @@ abstract class Make extends Command $class = str_replace($namespace . '\\', '', $name); - return str_replace(['{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [ $class, + Config::get('action_suffix'), $namespace, - App::$namespace, + App::getNamespace(), ], $stub); - } protected function getPathName($name) { - $name = str_replace(App::$namespace . '\\', '', $name); + $name = str_replace(App::getNamespace() . '\\', '', $name); - return APP_PATH . str_replace('\\', '/', $name) . '.php'; + return Env::get('app_path') . ltrim(str_replace('\\', '/', $name), '/') . '.php'; } protected function getClassName($name) { - $appNamespace = App::$namespace; + $appNamespace = App::getNamespace(); - if (strpos($name, $appNamespace . '\\') === 0) { + if (strpos($name, $appNamespace . '\\') !== false) { return $name; } diff --git a/Server/thinkphp/library/think/console/command/make/Controller.php b/Server/thinkphp/library/think/console/command/make/Controller.php index afa7be90..2a6ab770 100644 --- a/Server/thinkphp/library/think/console/command/make/Controller.php +++ b/Server/thinkphp/library/think/console/command/make/Controller.php @@ -11,30 +11,36 @@ namespace think\console\command\make; -use think\Config; use think\console\command\Make; use think\console\input\Option; +use think\facade\Config; class Controller extends Make { - protected $type = "Controller"; protected function configure() { parent::configure(); $this->setName('make:controller') + ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.') ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') ->setDescription('Create a new resource controller class'); } protected function getStub() { - if ($this->input->getOption('plain')) { - return __DIR__ . '/stubs/controller.plain.stub'; + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + if ($this->input->getOption('api')) { + return $stubPath . 'controller.api.stub'; } - return __DIR__ . '/stubs/controller.stub'; + if ($this->input->getOption('plain')) { + return $stubPath . 'controller.plain.stub'; + } + + return $stubPath . 'controller.stub'; } protected function getClassName($name) diff --git a/Server/thinkphp/library/think/console/command/make/Model.php b/Server/thinkphp/library/think/console/command/make/Model.php index d4e9b5dd..03e6b3fc 100644 --- a/Server/thinkphp/library/think/console/command/make/Model.php +++ b/Server/thinkphp/library/think/console/command/make/Model.php @@ -26,7 +26,7 @@ class Model extends Make protected function getStub() { - return __DIR__ . '/stubs/model.stub'; + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; } protected function getNamespace($appNamespace, $module) diff --git a/Server/thinkphp/library/think/console/command/make/stubs/controller.stub b/Server/thinkphp/library/think/console/command/make/stubs/controller.stub index 870ce5cf..4533c07d 100644 --- a/Server/thinkphp/library/think/console/command/make/stubs/controller.stub +++ b/Server/thinkphp/library/think/console/command/make/stubs/controller.stub @@ -12,7 +12,7 @@ class {%className%} extends Controller * * @return \think\Response */ - public function index() + public function index{%actionSuffix%}() { // } @@ -22,7 +22,7 @@ class {%className%} extends Controller * * @return \think\Response */ - public function create() + public function create{%actionSuffix%}() { // } @@ -33,7 +33,7 @@ class {%className%} extends Controller * @param \think\Request $request * @return \think\Response */ - public function save(Request $request) + public function save{%actionSuffix%}(Request $request) { // } @@ -44,7 +44,7 @@ class {%className%} extends Controller * @param int $id * @return \think\Response */ - public function read($id) + public function read{%actionSuffix%}($id) { // } @@ -55,7 +55,7 @@ class {%className%} extends Controller * @param int $id * @return \think\Response */ - public function edit($id) + public function edit{%actionSuffix%}($id) { // } @@ -67,7 +67,7 @@ class {%className%} extends Controller * @param int $id * @return \think\Response */ - public function update(Request $request, $id) + public function update{%actionSuffix%}(Request $request, $id) { // } @@ -78,7 +78,7 @@ class {%className%} extends Controller * @param int $id * @return \think\Response */ - public function delete($id) + public function delete{%actionSuffix%}($id) { // } diff --git a/Server/thinkphp/library/think/console/command/optimize/Autoload.php b/Server/thinkphp/library/think/console/command/optimize/Autoload.php index 6a77c2cb..b51fd259 100644 --- a/Server/thinkphp/library/think/console/command/optimize/Autoload.php +++ b/Server/thinkphp/library/think/console/command/optimize/Autoload.php @@ -10,15 +10,13 @@ // +---------------------------------------------------------------------- namespace think\console\command\optimize; -use think\App; -use think\Config; use think\console\Command; use think\console\Input; use think\console\Output; +use think\Container; class Autoload extends Command { - protected function configure() { $this->setName('optimize:autoload') @@ -37,20 +35,14 @@ class Autoload extends Command return [ EOF; - + $app = Container::get('app'); $namespacesToScan = [ - App::$namespace . '\\' => realpath(rtrim(APP_PATH)), - 'think\\' => LIB_PATH . 'think', - 'behavior\\' => LIB_PATH . 'behavior', - 'traits\\' => LIB_PATH . 'traits', - '' => realpath(rtrim(EXTEND_PATH)), + $app->getNamespace() . '\\' => realpath(rtrim($app->getAppPath())), + 'think\\' => $app->getThinkPath() . 'library/think', + 'traits\\' => $app->getThinkPath() . 'library/traits', + '' => realpath(rtrim($app->getRootPath() . 'extend')), ]; - $root_namespace = Config::get('root_namespace'); - foreach ($root_namespace as $namespace => $dir) { - $namespacesToScan[$namespace . '\\'] = realpath($dir); - } - krsort($namespacesToScan); $classMap = []; foreach ($namespacesToScan as $namespace => $dir) { @@ -59,7 +51,7 @@ EOF; continue; } - $namespaceFilter = $namespace === '' ? null : $namespace; + $namespaceFilter = '' === $namespace ? null : $namespace; $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); } @@ -68,12 +60,12 @@ EOF; $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; } $classmapFile .= "];\n"; - - if (!is_dir(RUNTIME_PATH)) { - @mkdir(RUNTIME_PATH, 0755, true); + $runtimePath = $app->getRuntimePath(); + if (!is_dir($runtimePath)) { + @mkdir($runtimePath, 0755, true); } - file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); + file_put_contents($runtimePath . 'classmap.php', $classmapFile); $output->writeln('Succeed!'); } @@ -100,40 +92,33 @@ EOF; protected function getPathCode($path) { - $baseDir = ''; - $libPath = $this->normalizePath(realpath(LIB_PATH)); - $appPath = $this->normalizePath(realpath(APP_PATH)); - $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); - $rootPath = $this->normalizePath(realpath(ROOT_PATH)); + $app = Container::get('app'); + $appPath = $this->normalizePath(realpath($app->getAppPath())); + $libPath = $this->normalizePath(realpath($app->getThinkPath() . 'library')); + $extendPath = $this->normalizePath(realpath($app->getRootPath() . 'extend')); $path = $this->normalizePath($path); - if ($libPath !== null && strpos($path, $libPath . '/') === 0) { - $path = substr($path, strlen(LIB_PATH)); - $baseDir = 'LIB_PATH'; - } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { + if (strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen($app->getThinkPath() . 'library')); + $baseDir = "'" . $libPath . "/'"; + } elseif (strpos($path, $appPath . '/') === 0) { $path = substr($path, strlen($appPath) + 1); - $baseDir = 'APP_PATH'; - } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { + $baseDir = "'" . $appPath . "/'"; + } elseif (strpos($path, $extendPath . '/') === 0) { $path = substr($path, strlen($extendPath) + 1); - $baseDir = 'EXTEND_PATH'; - } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { - $path = substr($path, strlen($rootPath) + 1); - $baseDir = 'ROOT_PATH'; + $baseDir = "'" . $extendPath . "/'"; } - if ($path !== false) { + if (false !== $path) { $baseDir .= " . "; } - return $baseDir . (($path !== false) ? var_export($path, true) : ""); + return $baseDir . ((false !== $path) ? var_export($path, true) : ""); } protected function normalizePath($path) { - if ($path === false) { - return; - } $parts = []; $path = strtr($path, '\\', '/'); $prefix = ''; @@ -252,7 +237,7 @@ EOF; // strip leading non-php code if needed if (substr($contents, 0, 2) !== 'setName('optimize:config') @@ -31,63 +29,79 @@ class Config extends Command protected function execute(Input $input, Output $output) { if ($input->getArgument('module')) { - $module = $input->getArgument('module') . DS; + $module = $input->getArgument('module') . DIRECTORY_SEPARATOR; } else { $module = ''; } - $content = 'buildCacheContent($module); - - if (!is_dir(RUNTIME_PATH . $module)) { - @mkdir(RUNTIME_PATH . $module, 0755, true); + $content = 'buildCacheContent($module); + $runtimePath = App::getRuntimePath(); + if (!is_dir($runtimePath . $module)) { + @mkdir($runtimePath . $module, 0755, true); } - file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); + file_put_contents($runtimePath . $module . 'init.php', $content); $output->writeln('Succeed!'); } protected function buildCacheContent($module) { - $content = ''; - $path = realpath(APP_PATH . $module) . DS; - + $content = '// This cache file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL; + $path = realpath(App::getAppPath() . $module) . DIRECTORY_SEPARATOR; if ($module) { - // 加载模块配置 - $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); + $configPath = is_dir($path . 'config') ? $path . 'config' : App::getConfigPath() . $module; + } else { + $configPath = App::getConfigPath(); + } + $ext = App::getConfigExt(); + $config = Container::get('config'); - // 读取数据库配置文件 - $filename = CONF_PATH . $module . 'database' . CONF_EXT; - ThinkConfig::load($filename, 'database'); + $files = is_dir($configPath) ? scandir($configPath) : []; - // 加载应用状态配置 - if ($config['app_status']) { - $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); - } - // 读取扩展配置文件 - if (is_dir(CONF_PATH . $module . 'extra')) { - $dir = CONF_PATH . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if (strpos($file, CONF_EXT)) { - $filename = $dir . DS . $file; - ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $ext) { + $filename = $configPath . DIRECTORY_SEPARATOR . $file; + $config->load($filename, pathinfo($file, PATHINFO_FILENAME)); } } // 加载行为扩展文件 - if (is_file(CONF_PATH . $module . 'tags' . EXT)) { - $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export($tags, true)) . ');' . PHP_EOL; + } } // 加载公共文件 - if (is_file($path . 'common' . EXT)) { - $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; + if (is_file($path . 'common.php')) { + $common = substr(php_strip_whitespace($path . 'common.php'), 6); + if ($common) { + $content .= PHP_EOL . $common . PHP_EOL; + } } - $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; + if ('' == $module) { + $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL; + + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $content .= PHP_EOL . '\think\Container::get("middleware")->import(' . var_export($middleware, true) . ');' . PHP_EOL; + } + } + } + + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $content .= PHP_EOL . '\think\Container::getInstance()->bindTo(' . var_export($provider, true) . ');' . PHP_EOL; + } + } + + $content .= PHP_EOL . '\think\facade\Config::set(' . var_export($config->get(), true) . ');' . PHP_EOL; + return $content; } } diff --git a/Server/thinkphp/library/think/console/command/optimize/Route.php b/Server/thinkphp/library/think/console/command/optimize/Route.php index 6da1d9a6..f6dc6328 100644 --- a/Server/thinkphp/library/think/console/command/optimize/Route.php +++ b/Server/thinkphp/library/think/console/command/optimize/Route.php @@ -6,19 +6,17 @@ // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- -// | Author: liu21st +// | Author: yunwuxin <448901948@qq.com> // +---------------------------------------------------------------------- namespace think\console\command\optimize; use think\console\Command; use think\console\Input; use think\console\Output; +use think\Container; class Route extends Command { - /** @var Output */ - protected $output; - protected function configure() { $this->setName('optimize:route') @@ -27,49 +25,42 @@ class Route extends Command protected function execute(Input $input, Output $output) { - - if (!is_dir(RUNTIME_PATH)) { - @mkdir(RUNTIME_PATH, 0755, true); + $filename = Container::get('app')->getRuntimePath() . 'route.php'; + if (is_file($filename)) { + unlink($filename); } - - file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); + file_put_contents($filename, $this->buildRouteCache()); $output->writeln('Succeed!'); } protected function buildRouteCache() { - $files = \think\Config::get('route_config_file'); + Container::get('route')->setName([]); + Container::get('route')->setTestMode(true); + // 路由检测 + $path = Container::get('app')->getRoutePath(); + + $files = is_dir($path) ? scandir($path) : []; + foreach ($files as $file) { - if (is_file(CONF_PATH . $file . CONF_EXT)) { - $config = include CONF_PATH . $file . CONF_EXT; - if (is_array($config)) { - \think\Route::import($config); + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + Container::get('route')->import($rules); } } } - $rules = \think\Route::rules(true); - array_walk_recursive($rules, [$this, 'buildClosure']); + + if (Container::get('config')->get('route_annotation')) { + $suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix'); + include Container::get('build')->buildRoute($suffix); + } + $content = 'getName(), true) . ';'; return $content; } - protected function buildClosure(&$value) - { - if ($value instanceof \Closure) { - $reflection = new \ReflectionFunction($value); - $startLine = $reflection->getStartLine(); - $endLine = $reflection->getEndLine(); - $file = $reflection->getFileName(); - $item = file($file); - $content = ''; - for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { - $content .= $item[$i]; - } - $start = strpos($content, 'function'); - $end = strrpos($content, '}'); - $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; - } - } } diff --git a/Server/thinkphp/library/think/console/command/optimize/Schema.php b/Server/thinkphp/library/think/console/command/optimize/Schema.php index 33534240..16ac83d5 100644 --- a/Server/thinkphp/library/think/console/command/optimize/Schema.php +++ b/Server/thinkphp/library/think/console/command/optimize/Schema.php @@ -10,22 +10,18 @@ // +---------------------------------------------------------------------- namespace think\console\command\optimize; -use think\App; use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; use think\Db; +use think\facade\App; class Schema extends Command { - /** @var Output */ - protected $output; - protected function configure() { $this->setName('optimize:schema') - ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .') ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') @@ -34,56 +30,58 @@ class Schema extends Command protected function execute(Input $input, Output $output) { - if (!is_dir(RUNTIME_PATH . 'schema')) { - @mkdir(RUNTIME_PATH . 'schema', 0755, true); - } - $config = []; - if ($input->hasOption('config')) { - $config = $input->getOption('config'); + if (!is_dir(App::getRuntimePath() . 'schema')) { + @mkdir(App::getRuntimePath() . 'schema', 0755, true); } + if ($input->hasOption('module')) { $module = $input->getOption('module'); // 读取模型 - $path = APP_PATH . $module . DS . 'model'; - $list = is_dir($path) ? scandir($path) : []; - $app = App::$namespace; + $path = App::getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $namespace = App::getNamespace(); + foreach ($list as $file) { if (0 === strpos($file, '.')) { continue; } - $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $class = '\\' . $namespace . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); $this->buildModelSchema($class); } + $output->writeln('Succeed!'); return; } elseif ($input->hasOption('table')) { $table = $input->getOption('table'); - if (!strpos($table, '.')) { - $dbName = Db::connect($config)->getConfig('database'); + if (false === strpos($table, '.')) { + $dbName = Db::getConfig('database'); } + $tables[] = $table; } elseif ($input->hasOption('db')) { $dbName = $input->getOption('db'); - $tables = Db::connect($config)->getTables($dbName); - } elseif (!\think\Config::get('app_multi_module')) { - $app = App::$namespace; - $path = APP_PATH . 'model'; - $list = is_dir($path) ? scandir($path) : []; + $tables = Db::getConnection()->getTables($dbName); + } elseif (!\think\facade\Config::get('app_multi_module')) { + $namespace = App::getNamespace(); + $path = App::getAppPath() . 'model'; + $list = is_dir($path) ? scandir($path) : []; + foreach ($list as $file) { if (0 === strpos($file, '.')) { continue; } - $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); $this->buildModelSchema($class); } + $output->writeln('Succeed!'); return; } else { - $tables = Db::connect($config)->getTables(); + $tables = Db::getConnection()->getTables(); } $db = isset($dbName) ? $dbName . '.' : ''; - $this->buildDataBaseSchema($tables, $db, $config); + $this->buildDataBaseSchema($tables, $db); $output->writeln('Succeed!'); } @@ -97,22 +95,24 @@ class Schema extends Command $content = 'getFields($table); $content .= var_export($info, true) . ';'; - file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); + + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . '.' . $table . '.php', $content); } } - protected function buildDataBaseSchema($tables, $db, $config) + protected function buildDataBaseSchema($tables, $db) { if ('' == $db) { - $dbName = Db::connect($config)->getConfig('database') . '.'; + $dbName = Db::getConfig('database') . '.'; } else { $dbName = $db; } + foreach ($tables as $table) { $content = 'getFields($db . $table); + $info = Db::getConnection()->getFields($db . $table); $content .= var_export($info, true) . ';'; - file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . $table . '.php', $content); } } } diff --git a/Server/thinkphp/library/think/console/output/Descriptor.php b/Server/thinkphp/library/think/console/output/Descriptor.php index 23dc6481..6d98d53c 100644 --- a/Server/thinkphp/library/think/console/output/Descriptor.php +++ b/Server/thinkphp/library/think/console/output/Descriptor.php @@ -69,7 +69,7 @@ class Descriptor * 描述参数 * @param InputArgument $argument * @param array $options - * @return void + * @return string|mixed */ protected function describeInputArgument(InputArgument $argument, array $options = []) { @@ -93,7 +93,7 @@ class Descriptor * 描述选项 * @param InputOption $option * @param array $options - * @return void + * @return string|mixed */ protected function describeInputOption(InputOption $option, array $options = []) { @@ -128,7 +128,7 @@ class Descriptor * 描述输入 * @param InputDefinition $definition * @param array $options - * @return void + * @return string|mixed */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { @@ -173,7 +173,7 @@ class Descriptor * 描述指令 * @param Command $command * @param array $options - * @return void + * @return string|mixed */ protected function describeCommand(Command $command, array $options = []) { @@ -208,7 +208,7 @@ class Descriptor * 描述控制台 * @param Console $console * @param array $options - * @return void + * @return string|mixed */ protected function describeConsole(Console $console, array $options = []) { diff --git a/Server/thinkphp/library/think/console/output/descriptor/Console.php b/Server/thinkphp/library/think/console/output/descriptor/Console.php index 4648b68e..8739c536 100644 --- a/Server/thinkphp/library/think/console/output/descriptor/Console.php +++ b/Server/thinkphp/library/think/console/output/descriptor/Console.php @@ -104,6 +104,10 @@ class Console /** @var Command $command */ foreach ($commands as $name => $command) { + if (is_string($command)) { + $command = new $command(); + } + if (!$command->getName()) { continue; } diff --git a/Server/thinkphp/library/think/console/output/driver/Console.php b/Server/thinkphp/library/think/console/output/driver/Console.php index 8f29fd02..e041b525 100644 --- a/Server/thinkphp/library/think/console/output/driver/Console.php +++ b/Server/thinkphp/library/think/console/output/driver/Console.php @@ -37,11 +37,6 @@ class Console $this->formatter->setDecorated($decorated); } - public function getFormatter() - { - return $this->formatter; - } - public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); @@ -173,7 +168,7 @@ class Console return $this->terminalDimensions; } - if ('\\' === DS) { + if ('\\' === DIRECTORY_SEPARATOR) { if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { return [(int) $matches[1], (int) $matches[2]]; } diff --git a/Server/thinkphp/library/think/console/output/question/Choice.php b/Server/thinkphp/library/think/console/output/question/Choice.php index f6760e5e..cdc3b4e4 100644 --- a/Server/thinkphp/library/think/console/output/question/Choice.php +++ b/Server/thinkphp/library/think/console/output/question/Choice.php @@ -147,7 +147,7 @@ class Choice extends Question $result = $value; } - if (empty($result)) { + if (false === $result) { throw new \InvalidArgumentException(sprintf($errorMessage, $value)); } array_push($multiselectChoices, $result); diff --git a/Server/thinkphp/library/think/db/Builder.php b/Server/thinkphp/library/think/db/Builder.php index 58b45aa8..60b470e8 100644 --- a/Server/thinkphp/library/think/db/Builder.php +++ b/Server/thinkphp/library/think/db/Builder.php @@ -18,29 +18,43 @@ abstract class Builder { // connection对象实例 protected $connection; - // 查询对象实例 - protected $query; - // 数据库表达式 - protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; + // 查询表达式映射 + protected $exp = ['EQ' => '=', 'NEQ' => '<>', 'GT' => '>', 'EGT' => '>=', 'LT' => '<', 'ELT' => '<=', 'NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; // SQL表达式 - protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT%%LOCK%%COMMENT%'; - protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; - protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; - protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $updateSql = 'UPDATE %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $deleteSql = 'DELETE FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; /** - * 构造函数 + * 架构函数 * @access public - * @param Connection $connection 数据库连接对象实例 - * @param Query $query 数据库查询对象实例 + * @param Connection $connection 数据库连接对象实例 */ - public function __construct(Connection $connection, Query $query) + public function __construct(Connection $connection) { $this->connection = $connection; - $this->query = $query; } /** @@ -54,303 +68,333 @@ abstract class Builder } /** - * 获取当前的Query对象实例 + * 注册查询表达式解析 * @access public - * @return Query + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this */ - public function getQuery() + public function bindParser($name, $parser) { - return $this->query; - } - - /** - * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) - * @access protected - * @param string $sql sql语句 - * @return string - */ - protected function parseSqlTable($sql) - { - return $this->query->parseSqlTable($sql); + $this->parser[$name] = $parser; + return $this; } /** * 数据分析 * @access protected - * @param array $data 数据 - * @param array $options 查询参数 + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 * @return array - * @throws Exception */ - protected function parseData($data, $options) + protected function parseData(Query $query, $data = [], $fields = [], $bind = []) { if (empty($data)) { return []; } + $options = $query->getOptions(); + // 获取绑定信息 - $bind = $this->query->getFieldsBind($options['table']); - if ('*' == $options['field']) { - $fields = array_keys($bind); - } else { - $fields = $options['field']; + if (empty($bind)) { + $bind = $this->connection->getFieldsBind($options['table']); + } + + if (empty($fields)) { + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } } $result = []; + foreach ($data as $key => $val) { if ('*' != $options['field'] && !in_array($key, $fields, true)) { continue; } - $item = $this->parseKey($key, $options, true); + $item = $this->parseKey($query, $key, true); + if ($val instanceof Expression) { $result[$item] = $val->getValue(); continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) { + $val = json_encode($val, JSON_UNESCAPED_UNICODE); } elseif (is_object($val) && method_exists($val, '__toString')) { // 对象数据写入 $val = $val->__toString(); } - if (false === strpos($key, '.') && !in_array($key, $fields, true)) { + + if (false !== strpos($key, '->')) { + list($key, $name) = explode('->', $key); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind) . ')'; + } elseif ('*' == $options['field'] && false === strpos($key, '.') && !in_array($key, $fields, true)) { if ($options['strict']) { throw new Exception('fields not exists:[' . $key . ']'); } } elseif (is_null($val)) { $result[$item] = 'NULL'; } elseif (is_array($val) && !empty($val)) { - switch (strtolower($val[0])) { - case 'inc': - $result[$item] = $item . '+' . floatval($val[1]); + switch (strtoupper($val[0])) { + case 'INC': + $result[$item] = $item . ' + ' . floatval($val[1]); break; - case 'dec': - $result[$item] = $item . '-' . floatval($val[1]); + case 'DEC': + $result[$item] = $item . ' - ' . floatval($val[1]); break; - case 'exp': + case 'EXP': throw new Exception('not support data:[' . $val[0] . ']'); } } elseif (is_scalar($val)) { // 过滤非标量数据 - if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { - $result[$item] = $val; - } else { - $key = str_replace('.', '_', $key); - $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':data__' . $key; - } + $result[$item] = $this->parseDataBind($query, $key, $val, $bind); } } + return $result; } /** - * 字段名分析 + * 数据绑定处理 * @access protected - * @param string $key - * @param array $options + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 * @return string */ - protected function parseKey($key, $options = [], $strict = false) + protected function parseDataBind(Query $query, $key, $data, $bind = []) { - return $key; + if ($data instanceof Expression) { + return $data->getValue(); + } + + $name = $query->bind($data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + + return ':' . $name; } /** - * value分析 - * @access protected - * @param mixed $value - * @param string $field - * @return string|array + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string */ - protected function parseValue($value, $field = '') + public function parseKey(Query $query, $key, $strict = false) { - if (is_string($value)) { - $value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); - } elseif (is_array($value)) { - $value = array_map([$this, 'parseValue'], $value); - } elseif (is_bool($value)) { - $value = $value ? '1' : '0'; - } elseif (is_null($value)) { - $value = 'null'; - } - return $value; + return $key instanceof Expression ? $key->getValue() : $key; } /** * field分析 * @access protected - * @param mixed $fields - * @param array $options + * @param Query $query 查询对象 + * @param mixed $fields 字段名 * @return string */ - protected function parseField($fields, $options = []) + protected function parseField(Query $query, $fields) { if ('*' == $fields || empty($fields)) { $fieldsStr = '*'; } elseif (is_array($fields)) { // 支持 'field1'=>'field2' 这样的字段别名定义 $array = []; + foreach ($fields as $key => $field) { - if ($field instanceof Expression) { - $array[] = $field->getValue(); - } elseif (!is_numeric($key)) { - $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options, true); + if (!is_numeric($key)) { + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); } else { - $array[] = $this->parseKey($field, $options); + $array[] = $this->parseKey($query, $field); } } + $fieldsStr = implode(',', $array); } + return $fieldsStr; } /** * table分析 * @access protected - * @param mixed $tables - * @param array $options + * @param Query $query 查询对象 + * @param mixed $tables 表名 * @return string */ - protected function parseTable($tables, $options = []) + protected function parseTable(Query $query, $tables) { - $item = []; + $item = []; + $options = $query->getOptions(); + foreach ((array) $tables as $key => $table) { if (!is_numeric($key)) { - $key = $this->parseSqlTable($key); - $item[] = $this->parseKey($key) . ' ' . (isset($options['alias'][$table]) ? $this->parseKey($options['alias'][$table]) : $this->parseKey($table)); + $key = $this->connection->parseSqlTable($key); + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); } else { - $table = $this->parseSqlTable($table); + $table = $this->connection->parseSqlTable($table); + if (isset($options['alias'][$table])) { - $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); } else { - $item[] = $this->parseKey($table); + $item[] = $this->parseKey($query, $table); } } } + return implode(',', $item); } /** * where分析 * @access protected - * @param mixed $where 查询条件 - * @param array $options 查询参数 + * @param Query $query 查询对象 + * @param mixed $where 查询条件 * @return string */ - protected function parseWhere($where, $options) + protected function parseWhere(Query $query, $where) { - $whereStr = $this->buildWhere($where, $options); + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + if (!empty($options['soft_delete'])) { // 附加软删除条件 list($field, $condition) = $options['soft_delete']; - $binds = $this->query->getFieldsBind($options['table']); + $binds = $this->connection->getFieldsBind($options['table']); $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; - $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, '', $binds); } + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; } /** * 生成查询条件SQL * @access public - * @param mixed $where - * @param array $options + * @param Query $query 查询对象 + * @param mixed $where 查询条件 * @return string */ - public function buildWhere($where, $options) + public function buildWhere(Query $query, $where) { if (empty($where)) { $where = []; } - if ($where instanceof Query) { - return $this->buildWhere($where->getOptions('where'), $options); - } - $whereStr = ''; - $binds = $this->query->getFieldsBind($options['table']); - foreach ($where as $key => $val) { + $binds = $this->connection->getFieldsBind($query->getOptions('table')); + + foreach ($where as $logic => $val) { $str = []; - foreach ($val as $field => $value) { + + foreach ($val as $value) { if ($value instanceof Expression) { - $str[] = ' ' . $key . ' ( ' . $value->getValue() . ' )'; - } elseif ($value instanceof \Closure) { - // 使用闭包查询 - $query = new Query($this->connection); - call_user_func_array($value, [ & $query]); - $whereClause = $this->buildWhere($query->getOptions('where'), $options); - if (!empty($whereClause)) { - $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + $str[] = ' ' . $logic . ' ( ' . $value->getValue() . ' )'; + continue; + } + + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof \Closure) { + // 使用闭包查询 + $newQuery = $query->newQuery()->setConnection($this->connection); + $value($newQuery); + $whereClause = $this->buildWhere($newQuery, $newQuery->getOptions('where')); + + if (!empty($whereClause)) { + $query->bind($newQuery->getBind(false)); + $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )'; + } + } elseif (is_array($field)) { + array_unshift($value, $field); + $str2 = []; + foreach ($value as $item) { + $str2[] = $this->parseWhereItem($query, array_shift($item), $item, $logic, $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $str2) . ' )'; } elseif (strpos($field, '|')) { // 不同字段使用相同查询条件(OR) $array = explode('|', $field); $item = []; + foreach ($array as $k) { - $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); } - $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; + + $str[] = ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; } elseif (strpos($field, '&')) { // 不同字段使用相同查询条件(AND) $array = explode('&', $field); $item = []; + foreach ($array as $k) { - $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); } - $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; } else { // 对字段使用表达式查询 $field = is_string($field) ? $field : ''; - $str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); + $str[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $logic, $binds); } } - $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); } return $whereStr; } // where子单元分析 - protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) + protected function parseWhereItem(Query $query, $field, $val, $rule = '', $binds = []) { // 字段分析 - $key = $field ? $this->parseKey($field, $options, true) : ''; + $key = $field ? $this->parseKey($query, $field, true) : ''; // 查询规则和条件 if (!is_array($val)) { - $val = is_null($val) ? ['null', ''] : ['=', $val]; + $val = is_null($val) ? ['NULL', ''] : ['=', $val]; } + list($exp, $value) = $val; // 对一个字段使用多个查询条件 if (is_array($exp)) { $item = array_pop($val); + // 传入 or 或者 and if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { $rule = $item; } else { array_push($val, $item); } + foreach ($val as $k => $item) { - $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; - $str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); + $str[] = $this->parseWhereItem($query, $field, $item, $rule, $binds); } + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; } // 检测操作符 - if (!in_array($exp, $this->exp)) { - $exp = strtolower($exp); - if (isset($this->exp[$exp])) { - $exp = $this->exp[$exp]; - } else { - throw new Exception('where express error:' . $exp); - } - } - $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field); - if (preg_match('/\W/', $bindName)) { - // 处理带非单词字符的字段名 - $bindName = md5($bindName); + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; } if ($value instanceof Expression) { @@ -360,146 +404,322 @@ abstract class Builder $value = $value->__toString(); } - $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; - if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { - if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { - if ($this->query->isBind($bindName)) { - $bindName .= '_' . str_replace('.', '_', uniqid('', true)); - } - $this->query->bind($bindName, $value, $bindType); - $value = ':' . $bindName; + if (strpos($field, '->')) { + $jsonType = $query->getJsonFieldType($field); + $bindType = $this->connection->getFieldBindType($jsonType); + } else { + $bindType = isset($binds[$field]) && 'LIKE' != $exp ? $binds[$field] : PDO::PARAM_STR; + } + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (0 === strpos($value, ':') && $query->isBind(substr($value, 1))) { + } else { + $name = $query->bind($value, $bindType); + $value = ':' . $name; } } - $whereStr = ''; - if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { - // 比较运算 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindType, isset($val[2]) ? $val[2] : 'AND'); + break; } - } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { - // 模糊匹配 - if (is_array($value)) { - foreach ($value as $item) { - $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); - } - $logic = isset($val[2]) ? $val[2] : 'AND'; - $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); - } - } elseif ('EXP' == $exp) { - // 表达式查询 - if ($value instanceof Expression) { - $whereStr .= '( ' . $key . ' ' . $value->getValue() . ' )'; - } else { - throw new Exception('where express error:' . $exp); - } - } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { - // NULL 查询 - $whereStr .= $key . ' IS ' . $exp; - } elseif (in_array($exp, ['NOT IN', 'IN'])) { - // IN 查询 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); - } else { - $value = array_unique(is_array($value) ? $value : explode(',', $value)); - if (array_key_exists($field, $binds)) { - $bind = []; - $array = []; - $i = 0; - foreach ($value as $v) { - $i++; - if ($this->query->isBind($bindName . '_in_' . $i)) { - $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; - } else { - $bindKey = $bindName . '_in_' . $i; - } - $bind[$bindKey] = [$v, $bindType]; - $array[] = ':' . $bindKey; - } - $this->query->bind($bind); - $zone = implode(',', $array); - } else { - $zone = implode(',', $this->parseValue($value, $field)); - } - $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; - } - } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { - // BETWEEN 查询 - $data = is_array($value) ? $value : explode(',', $value); - if (array_key_exists($field, $binds)) { - if ($this->query->isBind($bindName . '_between_1')) { - $bindKey1 = $bindName . '_between_1' . uniqid(); - $bindKey2 = $bindName . '_between_2' . uniqid(); - } else { - $bindKey1 = $bindName . '_between_1'; - $bindKey2 = $bindName . '_between_2'; - } - $bind = [ - $bindKey1 => [$data[0], $bindType], - $bindKey2 => [$data[1], $bindType], - ]; - $this->query->bind($bind); - $between = ':' . $bindKey1 . ' AND :' . $bindKey2; - } else { - $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); - } - $whereStr .= $key . ' ' . $exp . ' ' . $between; - } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { - // EXISTS 查询 - if ($value instanceof \Closure) { - $whereStr .= $exp . ' ' . $this->parseClosure($value); - } else { - $whereStr .= $exp . ' (' . $value . ')'; - } - } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { - $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); - } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { - if (is_string($value)) { - $value = explode(',', $value); - } - - $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); } + + if (!isset($whereStr)) { + throw new Exception('where express error:' . $exp); + } + return $whereStr; } - // 执行闭包子查询 - protected function parseClosure($call, $show = true) + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, $key, $exp, $value, $field, $bindType, $logic) { - $query = new Query($this->connection); - call_user_func_array($call, [ & $query]); - return $query->buildSql($show); + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $name = $query->bind($item, PDO::PARAM_STR); + $array[] = $key . ' ' . $exp . ' :' . $name; + } + + $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, $key, $exp, array $value, $field, $bindType) + { + // 字段比较查询 + list($op, $field2) = $value; + + if (!in_array($op, ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field2, true) . ' )'; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param Expression $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, $key, $exp, Expression $value, $field, $bindType) + { + // 表达式查询 + return '( ' . $key . ' ' . $value->getValue() . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, $key, $exp, $value, $field, $bindType) + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, $key, $exp, $value, $field, $bindType) + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + $min = $query->bind($data[0], $bindType); + $max = $query->bind($data[1], $bindType); + + return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' '; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, $key, $exp, $value, $field, $bindType) + { + // EXISTS 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Expression) { + $value = $value->getValue(); + } else { + throw new Exception('where express error:' . $value); + } + + return $exp . ' (' . $value . ')'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, $key, $exp, $value, $field, $bindType) + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, $key, $exp, $value, $field, $bindType) + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value); + } + + if ('=' == $exp && is_null($value)) { + return $key . ' IS NULL'; + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, $key, $exp, $value, $field, $bindType) + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, $key, $exp, $value, $field, $bindType) + { + // IN 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Expression) { + $value = $value->getValue(); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + $array = []; + + foreach ($value as $k => $v) { + $name = $query->bind($v, $bindType); + $array[] = ':' . $name; + } + + if (count($array) == 1) { + return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0]; + } else { + $zone = implode(',', $array); + $value = empty($zone) ? "''" : $zone; + } + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, $call, $show = true) + { + $newQuery = $query->newQuery()->removeOption(); + $call($newQuery); + + return $newQuery->buildSql($show); } /** * 日期时间条件解析 * @access protected - * @param string $value - * @param string $key - * @param array $options - * @param string $bindName - * @param integer $bindType + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @param integer $bindType * @return string */ - protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) + protected function parseDateTime(Query $query, $value, $key, $bindType = null) { + $options = $query->getOptions(); + // 获取时间字段类型 if (strpos($key, '.')) { list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { $table = $pos; } } else { $table = $options['table']; } - $type = $this->query->getTableInfo($table, 'type'); + + $type = $this->connection->getTableInfo($table, 'type'); + if (isset($type[$key])) { $info = $type[$key]; } + if (isset($info)) { if (is_string($value)) { $value = strtotime($value) ?: $value; @@ -513,23 +733,20 @@ abstract class Builder $value = date('Y-m-d', $value); } } - $bindName = $bindName ?: $key; - if ($this->query->isBind($bindName)) { - $bindName .= '_' . str_replace('.', '_', uniqid('', true)); - } + $name = $query->bind($value, $bindType); - $this->query->bind($bindName, $value, $bindType); - return ':' . $bindName; + return ':' . $name; } /** * limit分析 * @access protected - * @param mixed $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - protected function parseLimit($limit) + protected function parseLimit(Query $query, $limit) { return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; } @@ -537,88 +754,139 @@ abstract class Builder /** * join分析 * @access protected - * @param array $join - * @param array $options 查询条件 + * @param Query $query 查询对象 + * @param array $join * @return string */ - protected function parseJoin($join, $options = []) + protected function parseJoin(Query $query, $join) { $joinStr = ''; + if (!empty($join)) { foreach ($join as $item) { list($table, $type, $on) = $item; - $condition = []; + + $condition = []; + foreach ((array) $on as $val) { if ($val instanceof Expression) { $condition[] = $val->getValue(); } elseif (strpos($val, '=')) { list($val1, $val2) = explode('=', $val, 2); - $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + + $condition[] = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); } else { $condition[] = $val; } } - $table = $this->parseTable($table, $options); + $table = $this->parseTable($query, $table); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); } } + return $joinStr; } /** * order分析 * @access protected - * @param mixed $order - * @param array $options 查询条件 + * @param Query $query 查询对象 + * @param mixed $order * @return string */ - protected function parseOrder($order, $options = []) + protected function parseOrder(Query $query, $order) { - if (empty($order)) { - return ''; - } - - $array = []; foreach ($order as $key => $val) { if ($val instanceof Expression) { $array[] = $val->getValue(); + } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) { + $array[] = $this->parseOrderField($query, $key, $val); } elseif ('[rand]' == $val) { - $array[] = $this->parseRand(); - } else { + $array[] = $this->parseRand($query); + } elseif (is_string($val)) { if (is_numeric($key)) { list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); } else { $sort = $val; } - $sort = strtoupper($sort); - $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; - $array[] = $this->parseKey($key, $options, true) . $sort; + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } } } - $order = implode(',', $array); - return !empty($order) ? ' ORDER BY ' . $order : ''; + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * orderField分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $key + * @param array $val + * @return string + */ + protected function parseOrderField($query, $key, $val) + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + + $options = $query->getOptions(); + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; } /** * group分析 * @access protected - * @param mixed $group + * @param Query $query 查询对象 + * @param mixed $group * @return string */ - protected function parseGroup($group) + protected function parseGroup(Query $query, $group) { - return !empty($group) ? ' GROUP BY ' . $this->parseKey($group) : ''; + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); } /** * having分析 * @access protected - * @param string $having + * @param Query $query 查询对象 + * @param string $having * @return string */ - protected function parseHaving($having) + protected function parseHaving(Query $query, $having) { return !empty($having) ? ' HAVING ' . $having : ''; } @@ -626,24 +894,27 @@ abstract class Builder /** * comment分析 * @access protected - * @param string $comment + * @param Query $query 查询对象 + * @param string $comment * @return string */ - protected function parseComment($comment) + protected function parseComment(Query $query, $comment) { if (false !== strpos($comment, '*/')) { $comment = strstr($comment, '*/', true); } + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; } /** * distinct分析 * @access protected - * @param mixed $distinct + * @param Query $query 查询对象 + * @param mixed $distinct * @return string */ - protected function parseDistinct($distinct) + protected function parseDistinct(Query $query, $distinct) { return !empty($distinct) ? ' DISTINCT ' : ''; } @@ -651,33 +922,38 @@ abstract class Builder /** * union分析 * @access protected - * @param mixed $union + * @param Query $query 查询对象 + * @param mixed $union * @return string */ - protected function parseUnion($union) + protected function parseUnion(Query $query, $union) { if (empty($union)) { return ''; } + $type = $union['type']; unset($union['type']); + foreach ($union as $u) { if ($u instanceof \Closure) { - $sql[] = $type . ' ' . $this->parseClosure($u); + $sql[] = $type . ' ' . $this->parseClosure($query, $u); } elseif (is_string($u)) { - $sql[] = $type . ' ( ' . $this->parseSqlTable($u) . ' )'; + $sql[] = $type . ' ( ' . $this->connection->parseSqlTable($u) . ' )'; } } + return ' ' . implode(' ', $sql); } /** * index分析,可在操作链中指定需要强制使用的索引 * @access protected - * @param mixed $index + * @param Query $query 查询对象 + * @param mixed $index * @return string */ - protected function parseForce($index) + protected function parseForce(Query $query, $index) { if (empty($index)) { return ''; @@ -689,14 +965,15 @@ abstract class Builder /** * 设置锁机制 * @access protected - * @param bool|string $lock + * @param Query $query 查询对象 + * @param bool|string $lock * @return string */ - protected function parseLock($lock = false) + protected function parseLock(Query $query, $lock = false) { if (is_bool($lock)) { return $lock ? ' FOR UPDATE ' : ''; - } elseif (is_string($lock)) { + } elseif (is_string($lock) && !empty($lock)) { return ' ' . trim($lock) . ' '; } } @@ -704,196 +981,193 @@ abstract class Builder /** * 生成查询SQL * @access public - * @param array $options 表达式 + * @param Query $query 查询对象 * @return string */ - public function select($options = []) + public function select(Query $query) { - $sql = str_replace( + $options = $query->getOptions(); + + return str_replace( ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], [ - $this->parseTable($options['table'], $options), - $this->parseDistinct($options['distinct']), - $this->parseField($options['field'], $options), - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseGroup($options['group']), - $this->parseHaving($options['having']), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseUnion($options['union']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - $this->parseForce($options['force']), - ], $this->selectSql); - return $sql; + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); } /** - * 生成insert SQL + * 生成Insert SQL * @access public - * @param array $data 数据 - * @param array $options 表达式 - * @param bool $replace 是否replace + * @param Query $query 查询对象 + * @param bool $replace 是否replace * @return string */ - public function insert(array $data, $options = [], $replace = false) + public function insert(Query $query, $replace = false) { + $options = $query->getOptions(); + // 分析并处理数据 - $data = $this->parseData($data, $options); + $data = $this->parseData($query, $options['data']); if (empty($data)) { - return 0; + return ''; } + $fields = array_keys($data); $values = array_values($data); - $sql = str_replace( + return str_replace( ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), + $this->parseTable($query, $options['table']), implode(' , ', $fields), implode(' , ', $values), - $this->parseComment($options['comment']), - ], $this->insertSql); - - return $sql; + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); } /** * 生成insertall SQL * @access public - * @param array $dataSet 数据集 - * @param array $options 表达式 - * @param bool $replace 是否replace + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace * @return string - * @throws Exception */ - public function insertAll($dataSet, $options = [], $replace = false) + public function insertAll(Query $query, $dataSet, $replace = false) { + $options = $query->getOptions(); + // 获取合法的字段 if ('*' == $options['field']) { - $fields = array_keys($this->query->getFieldsType($options['table'])); + $allowFields = $this->connection->getTableFields($options['table']); } else { - $fields = $options['field']; + $allowFields = $options['field']; } + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + foreach ($dataSet as $data) { - foreach ($data as $key => $val) { - if (!in_array($key, $fields, true)) { - if ($options['strict']) { - throw new Exception('fields not exists:[' . $key . ']'); - } - unset($data[$key]); - } elseif (is_null($val)) { - $data[$key] = 'NULL'; - } elseif (is_scalar($val)) { - $data[$key] = $this->parseValue($val, $key); - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $data[$key] = $val->__toString(); - } else { - // 过滤掉非标量数据 - unset($data[$key]); - } - } - $value = array_values($data); - $values[] = 'SELECT ' . implode(',', $value); + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = 'SELECT ' . implode(',', array_values($data)); if (!isset($insertFields)) { $insertFields = array_keys($data); } } + $fields = []; + foreach ($insertFields as $field) { - $fields[] = $this->parseKey($field, $options, true); + $fields[] = $this->parseKey($query, $field); } return str_replace( ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), - implode(' , ', $insertFields), + $this->parseTable($query, $options['table']), + implode(' , ', $fields), implode(' UNION ALL ', $values), - $this->parseComment($options['comment']), - ], $this->insertAllSql); + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); } /** - * 生成select insert SQL + * 生成slect insert SQL * @access public - * @param array $fields 数据 - * @param string $table 数据表 - * @param array $options 表达式 + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 * @return string */ - public function selectInsert($fields, $table, $options) + public function selectInsert(Query $query, $fields, $table) { if (is_string($fields)) { $fields = explode(',', $fields); } - $fields = array_map([$this, 'parseKey'], $fields); - $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); - return $sql; + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field, true); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); } /** * 生成update SQL * @access public - * @param array $data 数据 - * @param array $options 表达式 + * @param Query $query 查询对象 * @return string */ - public function update($data, $options) + public function update(Query $query) { - $table = $this->parseTable($options['table'], $options); - $data = $this->parseData($data, $options); + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + if (empty($data)) { return ''; } + foreach ($data as $key => $val) { - $set[] = $key . '=' . $val; + $set[] = $key . ' = ' . $val; } - $sql = str_replace( + return str_replace( ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table'], $options), - implode(',', $set), - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - ], $this->updateSql); - - return $sql; + $this->parseTable($query, $options['table']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); } /** * 生成delete SQL * @access public - * @param array $options 表达式 + * @param Query $query 查询对象 * @return string */ - public function delete($options) + public function delete(Query $query) { - $sql = str_replace( + $options = $query->getOptions(); + + return str_replace( ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table'], $options), - !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - ], $this->deleteSql); - - return $sql; + $this->parseTable($query, $options['table']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); } } diff --git a/Server/thinkphp/library/think/db/Connection.php b/Server/thinkphp/library/think/db/Connection.php index 578cc8f9..18b4885a 100644 --- a/Server/thinkphp/library/think/db/Connection.php +++ b/Server/thinkphp/library/think/db/Connection.php @@ -11,25 +11,21 @@ namespace think\db; +use InvalidArgumentException; use PDO; use PDOStatement; +use think\Container; use think\Db; use think\db\exception\BindParamException; use think\Debug; use think\Exception; use think\exception\PDOException; -use think\Log; +use think\Loader; -/** - * Class Connection - * @package think - * @method Query table(string $table) 指定数据表(含前缀) - * @method Query name(string $name) 指定数据表(不含前缀) - * - */ abstract class Connection { - + const PARAM_FLOAT = 21; + protected static $instance = []; /** @var PDOStatement PDO操作实例 */ protected $PDOStatement; @@ -56,7 +52,13 @@ abstract class Connection protected $attrCase = PDO::CASE_LOWER; // 监听回调 protected static $event = []; + + // 数据表信息 + protected static $info = []; + // 使用Builder类 + protected $builderClassName; + // Builder对象 protected $builder; // 数据库连接参数配置 protected $config = [ @@ -94,10 +96,8 @@ abstract class Connection 'read_master' => false, // 是否严格检查字段是否存在 'fields_strict' => true, - // 数据返回类型 - 'result_type' => PDO::FETCH_ASSOC, // 数据集返回类型 - 'resultset_type' => 'array', + 'resultset_type' => '', // 自动写入时间戳字段 'auto_timestamp' => false, // 时间字段取出后的默认时间格式 @@ -110,6 +110,8 @@ abstract class Connection 'query' => '\\think\\db\\Query', // 是否需要断线重连 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], ]; // PDO连接参数 @@ -121,30 +123,82 @@ abstract class Connection PDO::ATTR_EMULATE_PREPARES => false, ]; + // 服务器断线标识字符 + protected $breakMatchStr = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + ]; + // 绑定参数 protected $bind = []; /** - * 构造函数 读取数据库配置信息 + * 架构函数 读取数据库配置信息 * @access public - * @param array $config 数据库配置数组 + * @param array $config 数据库配置数组 */ public function __construct(array $config = []) { if (!empty($config)) { $this->config = array_merge($this->config, $config); } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + + // 执行初始化操作 + $this->initialize(); } /** - * 获取新的查询对象 + * 初始化 * @access protected - * @return Query + * @return void */ - protected function getQuery() + protected function initialize() + {} + + /** + * 取得数据库连接类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function instance($config = [], $name = false) { - $class = $this->config['query']; - return new $class($this); + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + if (empty($config['type'])) { + throw new InvalidArgumentException('Undefined db type'); + } + + // 记录初始化信息 + Container::get('app')->log('[ DB ] INIT ' . $config['type']); + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = Loader::factory($config['type'], '\\think\\db\\connector\\', $config); + } + + return self::$instance[$name]; } /** @@ -152,31 +206,42 @@ abstract class Connection * @access public * @return string */ - public function getBuilder() + public function getBuilderClass() { - if (!empty($this->builder)) { - return $this->builder; - } else { - return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + if (!empty($this->builderClassName)) { + return $this->builderClassName; } + + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); } /** - * 调用Query类的查询方法 - * @access public - * @param string $method 方法名称 - * @param array $args 调用参数 - * @return mixed + * 设置当前的数据库Builder对象 + * @access protected + * @param Builder $builder + * @return void */ - public function __call($method, $args) + protected function setBuilder(Builder $builder) { - return call_user_func_array([$this->getQuery(), $method], $args); + $this->builder = $builder; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; } /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ abstract protected function parseDsn($config); @@ -184,7 +249,7 @@ abstract class Connection /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ abstract public function getFields($tableName); @@ -200,7 +265,7 @@ abstract class Connection /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ abstract protected function getExplain($sql); @@ -208,7 +273,7 @@ abstract class Connection /** * 对返数据表字段信息进行大小写转换出来 * @access public - * @param array $info 字段信息 + * @param array $info 字段信息 * @return array */ public function fieldCase($info) @@ -225,13 +290,174 @@ abstract class Connection default: // 不做转换 } + return $info; } + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $bind = self::PARAM_FLOAT; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + + return $bind; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) { + return $this->getConfig('prefix') . strtolower($match[1]); + }, $sql); + } + + return $sql; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($tableName) = explode(' ', $tableName); + + if (false === strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset(self::$info[$schema])) { + // 读取缓存 + $cacheFile = Container::get('app')->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $schema . '.php'; + + if (!$this->config['debug'] && is_file($cacheFile)) { + $info = include $cacheFile; + } else { + $info = $this->getFields($tableName); + } + + $fields = array_keys($info); + $bind = $type = []; + + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + + self::$info[$schema] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + + return $fetch ? self::$info[$schema][$fetch] : self::$info[$schema]; + } + + /** + * 获取数据表的主键 + * @access public + * @param string $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName) + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName) + { + return $this->getTableInfo($tableName, 'bind'); + } + /** * 获取数据库的配置参数 * @access public - * @param string $config 配置名称 + * @param string $config 配置名称 * @return mixed */ public function getConfig($config = '') @@ -242,8 +468,8 @@ abstract class Connection /** * 设置数据库的配置参数 * @access public - * @param string|array $config 配置名称 - * @param mixed $value 配置值 + * @param string|array $config 配置名称 + * @param mixed $value 配置值 * @return void */ public function setConfig($config, $value = '') @@ -258,55 +484,63 @@ abstract class Connection /** * 连接数据库方法 * @access public - * @param array $config 连接参数 - * @param integer $linkNum 连接序号 - * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) * @return PDO * @throws Exception */ public function connect(array $config = [], $linkNum = 0, $autoConnection = false) { - if (!isset($this->links[$linkNum])) { - if (!$config) { - $config = $this->config; - } else { - $config = array_merge($this->config, $config); - } - // 连接参数 - if (isset($config['params']) && is_array($config['params'])) { - $params = $config['params'] + $this->params; - } else { - $params = $this->params; - } - // 记录当前字段属性大小写设置 - $this->attrCase = $params[PDO::ATTR_CASE]; + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } - // 数据返回类型 - if (isset($config['result_type'])) { - $this->fetchType = $config['result_type']; + if (!$config) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); } - try { - if (empty($config['dsn'])) { - $config['dsn'] = $this->parseDsn($config); - } - if ($config['debug']) { - $startTime = microtime(true); - } - $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); - if ($config['debug']) { - // 记录数据库连接信息 - Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); - } - } catch (\PDOException $e) { - if ($autoConnection) { - Log::record($e->getMessage(), 'error'); - return $this->connect($autoConnection, $linkNum); - } else { - throw $e; - } + + if ($config['debug']) { + $startTime = microtime(true); + } + + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + + if ($config['debug']) { + // 记录数据库连接信息 + $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; } } - return $this->links[$linkNum]; } /** @@ -327,36 +561,99 @@ abstract class Connection { if (!$this->linkID) { return false; + } + + return $this->linkID; + } + + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param Model $model 模型对象实例 + * @param array $condition 查询条件 + * @param mixed $relation 关联查询 + * @return \Generator + */ + public function getCursor($sql, $bind = [], $master = false, $model = null, $condition = null, $relation = null) + { + $this->initConnect($master); + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + Db::$queryTimes++; + + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); } else { - return $this->linkID; + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false, '', $master); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + $instance = $model->newInstance($result, $condition); + + if ($relation) { + $instance->relationQuery($relation); + } + + yield $instance; + } else { + yield $result; + } } } /** * 执行查询 返回数据集 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param bool $master 是否在主服务器读操作 - * @param bool $pdo 是否返回PDO对象 - * @return mixed - * @throws PDOException + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return array + * @throws BindParamException + * @throws \PDOException * @throws \Exception + * @throws \Throwable */ public function query($sql, $bind = [], $master = false, $pdo = false) { $this->initConnect($master); + if (!$this->linkID) { return false; } // 记录SQL语句 $this->queryStr = $sql; - if ($bind) { - $this->bind = $bind; - } + + $this->bind = $bind; Db::$queryTimes++; + try { // 调试开始 $this->debug(true); @@ -366,32 +663,39 @@ abstract class Connection // 是否为存储过程调用 $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 if ($procedure) { $this->bindParam($bind); } else { $this->bindValue($bind); } + // 执行查询 $this->PDOStatement->execute(); + // 调试结束 $this->debug(false, '', $master); + // 返回结果集 return $this->getResult($pdo, $procedure); } catch (\PDOException $e) { if ($this->isBreak($e)) { return $this->close()->query($sql, $bind, $master, $pdo); } + throw new PDOException($e, $this->config, $this->getLastsql()); } catch (\Throwable $e) { if ($this->isBreak($e)) { return $this->close()->query($sql, $bind, $master, $pdo); } + throw $e; } catch (\Exception $e) { if ($this->isBreak($e)) { return $this->close()->query($sql, $bind, $master, $pdo); } + throw $e; } } @@ -403,21 +707,23 @@ abstract class Connection * @param array $bind 参数绑定 * @param Query $query 查询对象 * @return int - * @throws PDOException + * @throws BindParamException + * @throws \PDOException * @throws \Exception + * @throws \Throwable */ public function execute($sql, $bind = [], Query $query = null) { $this->initConnect(true); + if (!$this->linkID) { return false; } // 记录SQL语句 $this->queryStr = $sql; - if ($bind) { - $this->bind = $bind; - } + + $this->bind = $bind; Db::$executeTimes++; try { @@ -429,14 +735,17 @@ abstract class Connection // 是否为存储过程调用 $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 if ($procedure) { $this->bindParam($bind); } else { $this->bindValue($bind); } + // 执行语句 $this->PDOStatement->execute(); + // 调试结束 $this->debug(false, '', true); @@ -445,30 +754,707 @@ abstract class Connection } $this->numRows = $this->PDOStatement->rowCount(); + return $this->numRows; } catch (\PDOException $e) { if ($this->isBreak($e)) { return $this->close()->execute($sql, $bind, $query); } + throw new PDOException($e, $this->config, $this->getLastsql()); } catch (\Throwable $e) { if ($this->isBreak($e)) { return $this->close()->execute($sql, $bind, $query); } + throw $e; } catch (\Exception $e) { if ($this->isBreak($e)) { return $this->close()->execute($sql, $bind, $query); } + throw $e; } } + /** + * 查找单条记录 + * @access public + * @param Query $query 查询对象 + * @return array|null|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + + $data = $options['data']; + $query->setOption('limit', 1); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + if (is_string($cache['key'])) { + $key = $cache['key']; + } else { + $key = $this->getCacheKey($query, $data); + } + + $result = Container::get('cache')->get($key); + + if (false !== $result) { + return $result; + } + } + + if (is_string($pk) && !is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + + $query->setOption('data', $data); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 事件回调 + $result = $query->trigger('before_find'); + + if (!$result) { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + + return $result; + } + + /** + * 使用游标查询记录 + * @access public + * @param Query $query 查询对象 + * @return \Generator + */ + public function cursor(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + $condition = isset($options['where']['AND']) ? $options['where']['AND'] : null; + $relation = isset($options['relaltion']) ? $options['relation'] : null; + + // 执行查询操作 + return $this->getCursor($sql, $bind, $options['master'], $query->getModel(), $condition, $relation); + } + + /** + * 查找记录 + * @access public + * @param Query $query 查询对象 + * @return array|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + $resultSet = $this->getCacheData($query, $options['cache'], null, $key); + + if (false !== $resultSet) { + return $resultSet; + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + $resultSet = $query->trigger('before_select'); + + if (!$resultSet) { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (!empty($options['cache']) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $options['cache']); + } + + return $resultSet; + } + + /** + * 插入记录 + * @access public + * @param Query $query 查询对象 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(Query $query, $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query, $replace); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); + + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $query->trigger('after_insert'); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param Query $query 查询对象 + * @param mixed $dataSet 数据集 + * @param bool $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(Query $query, $dataSet = [], $replace = false, $limit = null) + { + if (!is_array(reset($dataSet))) { + return false; + } + + $options = $query->getOptions(); + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item, $replace); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + $fetchSql[] = $this->getRealSql($sql, $bind); + } else { + $count += $this->execute($sql, $bind, $query); + } + } + + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + + return isset($fetchSql) ? implode(';', $fetchSql) : $count; + } + + $sql = $this->builder->insertAll($query, $dataSet, $replace); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + return $this->getRealSql($sql, $bind); + } + + return $this->execute($sql, $bind, $query); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param Query $query 查询对象 + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert(Query $query, $fields, $table) + { + // 分析查询表达式 + $options = $query->getOptions(); + + $table = $this->parseSqlTable($table); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + return $this->getRealSql($sql, $bind); + } + + return $this->execute($sql, $bind, $query); + } + + /** + * 更新记录 + * @access public + * @param Query $query 查询对象 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(Query $query) + { + $options = $query->getOptions(); + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + $pk = $query->getPk($options); + $data = $options['data']; + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = [$pk, '=', $data[$pk]]; + if (!isset($key)) { + $key = $this->getCacheKey($query, $data[$pk]); + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = [$field, '=', $data[$field]]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + $query->setOption('where', ['AND' => $where]); + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } + } + + // 更新数据 + $query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); + + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + + $query->setOption('data', $data); + $query->trigger('after_update'); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param Query $query 查询对象 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + $data = $options['data']; + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } elseif (!is_null($data) && true !== $data && !is_array($data)) { + $key = $this->getCacheKey($query, $data); + } elseif (is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = $this->execute($sql, $bind, $query); + + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + + $options['data'] = $data; + + $query->trigger('after_delete'); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one 是否返回一个值 + * @return mixed + */ + public function value(Query $query, $field, $default = null, $one = true) + { + $options = $query->getOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $key); + + if (false !== $result) { + return $result; + } + } + + if ($one) { + $query->setOption('limit', 1); + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + $result = $pdo->fetchColumn(); + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $aggregate 聚合方法 + * @param mixed $field 字段名 + * @return mixed + */ + public function aggregate(Query $query, $aggregate, $field) + { + if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) { + list($distinct, $field) = explode(' ', $field); + } + + $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS tp_' . strtolower($aggregate); + + return $this->value($query, $field, 0, false); + } + + /** + * 得到某个列的数组 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(Query $query, $field, $key = '') + { + $options = $query->getOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_null($field)) { + $field = ['*']; + } elseif (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + if ($key && ['*'] != $field) { + array_unshift($field, $key); + $field = array_unique($field); + } + + $query->setOption('field', $field); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $guid); + + if (false !== $result) { + return $result; + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + // 还原field参数 + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if (['*'] == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + + if (2 == $count) { + $column = $key2; + } elseif (1 == $count) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; + } + } + + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + + return $result; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function pdo(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + /** * 根据参数绑定组装最终的SQL语句 便于调试 * @access public - * @param string $sql 带参数绑定的sql语句 - * @param array $bind 参数绑定列表 + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 * @return string */ public function getRealSql($sql, array $bind = []) @@ -480,19 +1466,19 @@ abstract class Connection foreach ($bind as $key => $val) { $value = is_array($val) ? $val[0] : $val; $type = is_array($val) ? $val[1] : PDO::PARAM_STR; - if (PDO::PARAM_STR == $type) { - $value = $this->quote($value); - } elseif (PDO::PARAM_INT == $type) { - $value = (float) $value; + + if ((self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) && is_string($value)) { + $value = '\'' . addslashes($value) . '\''; + } elseif (PDO::PARAM_INT == $type && '' === $value) { + $value = 0; } + // 判断占位符 $sql = is_numeric($key) ? substr_replace($sql, $value, strpos($sql, '?'), 1) : - str_replace( - [':' . $key . ')', ':' . $key . ',', ':' . $key . ' ', ':' . $key . PHP_EOL], - [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], - $sql . ' '); + substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); } + return rtrim($sql); } @@ -501,7 +1487,7 @@ abstract class Connection * 支持 ['name'=>'value','id'=>123] 对应命名占位符 * 或者 ['value',123] 对应问号占位符 * @access public - * @param array $bind 要绑定的参数列表 + * @param array $bind 要绑定的参数列表 * @return void * @throws BindParamException */ @@ -509,15 +1495,21 @@ abstract class Connection { foreach ($bind as $key => $val) { // 占位符 - $param = is_numeric($key) ? $key + 1 : ':' . $key; + $param = is_int($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; } + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); } else { $result = $this->PDOStatement->bindValue($param, $val); } + if (!$result) { throw new BindParamException( "Error occurred when binding parameters '{$param}'", @@ -532,22 +1524,25 @@ abstract class Connection /** * 存储过程的输入输出参数绑定 * @access public - * @param array $bind 要绑定的参数列表 + * @param array $bind 要绑定的参数列表 * @return void * @throws BindParamException */ protected function bindParam($bind) { foreach ($bind as $key => $val) { - $param = is_numeric($key) ? $key + 1 : ':' . $key; + $param = is_int($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { array_unshift($val, $param); $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); } else { $result = $this->PDOStatement->bindValue($param, $val); } + if (!$result) { $param = array_shift($val); + throw new BindParamException( "Error occurred when binding parameters '{$param}'", $this->config, @@ -561,9 +1556,9 @@ abstract class Connection /** * 获得数据集数组 * @access protected - * @param bool $pdo 是否返回PDOStatement - * @param bool $procedure 是否存储过程 - * @return PDOStatement|array + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return array */ protected function getResult($pdo = false, $procedure = false) { @@ -571,12 +1566,16 @@ abstract class Connection // 返回PDOStatement对象处理 return $this->PDOStatement; } + if ($procedure) { // 存储过程返回结果 return $this->procedure(); } - $result = $this->PDOStatement->fetchAll($this->fetchType); + + $result = $this->PDOStatement->fetchAll($this->fetchType); + $this->numRows = count($result); + return $result; } @@ -588,20 +1587,23 @@ abstract class Connection protected function procedure() { $item = []; + do { $result = $this->getResult(); if ($result) { $item[] = $result; } } while ($this->PDOStatement->nextRowset()); + $this->numRows = count($item); + return $item; } /** * 执行数据库事务 * @access public - * @param callable $callback 数据操作方法回调 + * @param callable $callback 数据操作方法回调 * @return mixed * @throws PDOException * @throws \Exception @@ -610,11 +1612,13 @@ abstract class Connection public function transaction($callback) { $this->startTrans(); + try { $result = null; if (is_callable($callback)) { $result = call_user_func_array($callback, [$this]); } + $this->commit(); return $result; } catch (\Exception $e) { @@ -626,10 +1630,47 @@ abstract class Connection } } + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa($xid) + {} + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa($xid) + {} + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa($xid) + {} + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa($xid) + {} + /** * 启动事务 * @access public - * @return bool|mixed + * @return void + * @throws \PDOException * @throws \Exception */ public function startTrans() @@ -640,6 +1681,7 @@ abstract class Connection } ++$this->transTimes; + try { if (1 == $this->transTimes) { $this->linkID->beginTransaction(); @@ -648,19 +1690,12 @@ abstract class Connection $this->parseSavepoint('trans' . $this->transTimes) ); } - } catch (\Exception $e) { if ($this->isBreak($e)) { --$this->transTimes; return $this->close()->startTrans(); } throw $e; - } catch (\Error $e) { - if ($this->isBreak($e)) { - --$this->transTimes; - return $this->close()->startTrans(); - } - throw $e; } } @@ -713,7 +1748,8 @@ abstract class Connection /** * 生成定义保存点的SQL - * @param $name + * @access protected + * @param $name * @return string */ protected function parseSavepoint($name) @@ -723,7 +1759,8 @@ abstract class Connection /** * 生成回滚到保存点的SQL - * @param $name + * @access protected + * @param $name * @return string */ protected function parseSavepointRollBack($name) @@ -735,19 +1772,22 @@ abstract class Connection * 批处理执行SQL语句 * 批处理的指令都认为是execute操作 * @access public - * @param array $sqlArray SQL批处理指令 + * @param array $sqlArray SQL批处理指令 + * @param array $bind 参数绑定 * @return boolean */ - public function batchQuery($sqlArray = [], $bind = [], Query $query = null) + public function batchQuery($sqlArray = [], $bind = []) { if (!is_array($sqlArray)) { return false; } + // 自动启动事务支持 $this->startTrans(); + try { foreach ($sqlArray as $sql) { - $this->execute($sql, $bind, $query); + $this->execute($sql, $bind); } // 提交事务 $this->commit(); @@ -762,7 +1802,7 @@ abstract class Connection /** * 获得查询次数 * @access public - * @param boolean $execute 是否包含所有查询 + * @param boolean $execute 是否包含所有查询 * @return integer */ public function getQueryTimes($execute = false) @@ -791,15 +1831,17 @@ abstract class Connection $this->linkWrite = null; $this->linkRead = null; $this->links = []; + // 释放查询 $this->free(); + return $this; } /** * 是否断线 * @access protected - * @param \PDOException|\Exception $e 异常对象 + * @param \PDOException|\Exception $e 异常对象 * @return bool */ protected function isBreak($e) @@ -808,23 +1850,9 @@ abstract class Connection return false; } - $info = [ - 'server has gone away', - 'no connection to the server', - 'Lost connection', - 'is dead or not enabled', - 'Error while sending', - 'decryption failed or bad record mac', - 'server closed the connection unexpectedly', - 'SSL connection has been closed unexpectedly', - 'Error writing data to the connection', - 'Resource deadlock avoided', - 'failed with errno', - ]; - $error = $e->getMessage(); - foreach ($info as $msg) { + foreach ($this->breakMatchStr as $msg) { if (false !== stripos($error, $msg)) { return true; } @@ -845,7 +1873,7 @@ abstract class Connection /** * 获取最近插入的ID * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return string */ public function getLastInsID($sequence = null) @@ -876,51 +1904,44 @@ abstract class Connection } else { $error = ''; } + if ('' != $this->queryStr) { $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); } - return $error; - } - /** - * SQL指令安全过滤 - * @access public - * @param string $str SQL字符串 - * @param bool $master 是否主库查询 - * @return string - */ - public function quote($str, $master = true) - { - $this->initConnect($master); - return $this->linkID ? $this->linkID->quote($str) : $str; + return $error; } /** * 数据库调试 记录当前SQL及分析性能 * @access protected - * @param boolean $start 调试开始标记 true 开始 false 结束 - * @param string $sql 执行的SQL语句 留空自动获取 - * @param boolean $master 主从标记 + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 * @return void */ protected function debug($start, $sql = '', $master = false) { if (!empty($this->config['debug'])) { // 开启数据库调试模式 + $debug = Container::get('debug'); + if ($start) { - Debug::remark('queryStartTime', 'time'); + $debug->remark('queryStartTime', 'time'); } else { // 记录操作结束时间 - Debug::remark('queryEndTime', 'time'); - $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); + $debug->remark('queryEndTime', 'time'); + $runtime = $debug->getRangeTime('queryStartTime', 'queryEndTime'); $sql = $sql ?: $this->getLastsql(); $result = []; + // SQL性能分析 if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { $result = $this->getExplain($sql); } + // SQL监听 - $this->trigger($sql, $runtime, $result, $master); + $this->triggerSql($sql, $runtime, $result, $master); } } } @@ -928,7 +1949,7 @@ abstract class Connection /** * 监听SQL执行 * @access public - * @param callable $callback 回调方法 + * @param callable $callback 回调方法 * @return void */ public function listen($callback) @@ -939,13 +1960,13 @@ abstract class Connection /** * 触发SQL事件 * @access protected - * @param string $sql SQL语句 - * @param float $runtime SQL运行时间 - * @param mixed $explain SQL分析 - * @param bool $master 主从标记 + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 + * @param bool $master 主从标记 * @return void */ - protected function trigger($sql, $runtime, $explain = [], $master = false) + protected function triggerSql($sql, $runtime, $explain = [], $master = false) { if (!empty(self::$event)) { foreach (self::$event as $callback) { @@ -954,7 +1975,6 @@ abstract class Connection } } } else { - // 未注册监听则记录到日志中 if ($this->config['deploy']) { // 分布式记录当前操作的主从 $master = $master ? 'master|' : 'slave|'; @@ -962,17 +1982,24 @@ abstract class Connection $master = ''; } - Log::record('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]', 'sql'); + // 未注册监听则记录到日志中 + $this->log('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]'); + if (!empty($explain)) { - Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql'); + $this->log('[ EXPLAIN : ' . var_export($explain, true) . ' ]'); } } } + public function log($log, $type = 'sql') + { + $this->config['debug'] && Container::get('log')->record($log, $type); + } + /** * 初始化数据库连接 * @access protected - * @param boolean $master 是否主服务器 + * @param boolean $master 是否主服务器 * @return void */ protected function initConnect($master = true) @@ -983,11 +2010,13 @@ abstract class Connection if (!$this->linkWrite) { $this->linkWrite = $this->multiConnect(true); } + $this->linkID = $this->linkWrite; } else { if (!$this->linkRead) { $this->linkRead = $this->multiConnect(false); } + $this->linkID = $this->linkRead; } } elseif (!$this->linkID) { @@ -999,15 +2028,16 @@ abstract class Connection /** * 连接分布式服务器 * @access protected - * @param boolean $master 主服务器 + * @param boolean $master 主服务器 * @return PDO */ protected function multiConnect($master = false) { $_config = []; + // 分布式数据库配置解析 foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { - $_config[$name] = explode(',', $this->config[$name]); + $_config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; } // 主服务器序号 @@ -1030,16 +2060,20 @@ abstract class Connection $r = floor(mt_rand(0, count($_config['hostname']) - 1)); } $dbMaster = false; + if ($m != $r) { $dbMaster = []; foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; } } + $dbConfig = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; } + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); } @@ -1049,11 +2083,70 @@ abstract class Connection */ public function __destruct() { - // 释放查询 - if ($this->PDOStatement) { - $this->free(); - } // 关闭连接 $this->close(); } + + /** + * 缓存数据 + * @access protected + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + $cache = Container::get('cache'); + + if (isset($config['tag'])) { + $cache->tag($config['tag'])->set($key, $data, $config['expire']); + } else { + $cache->set($key, $data, $config['expire']); + } + } + + /** + * 获取缓存数据 + * @access protected + * @param Query $query 查询对象 + * @param mixed $cache 缓存设置 + * @param array $options 缓存 + * @return mixed + */ + protected function getCacheData(Query $query, $cache, $data, &$key = null) + { + // 判断查询缓存 + $key = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $data); + + return Container::get('cache')->get($key); + } + + /** + * 生成缓存标识 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 缓存数据 + * @return string + */ + protected function getCacheKey(Query $query, $value) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'], true) && is_scalar($value[2])) { + $data = $value[2]; + } + + $prefix = 'think:' . $this->getConfig('database') . '.'; + + if (isset($data)) { + return $prefix . $query->getTable() . '|' . $data; + } + + try { + return md5($prefix . serialize($query->getOptions()) . serialize($query->getBind(false))); + } catch (\Exception $e) { + throw new Exception('closure not support cache(true)'); + } + } + } diff --git a/Server/thinkphp/library/think/db/Query.php b/Server/thinkphp/library/think/db/Query.php index bb16a1f5..ba082794 100644 --- a/Server/thinkphp/library/think/db/Query.php +++ b/Server/thinkphp/library/think/db/Query.php @@ -12,10 +12,8 @@ namespace think\db; use PDO; -use think\App; -use think\Cache; use think\Collection; -use think\Config; +use think\Container; use think\Db; use think\db\exception\BindParamException; use think\db\exception\DataNotFoundException; @@ -25,73 +23,172 @@ use think\exception\DbException; use think\exception\PDOException; use think\Loader; use think\Model; +use think\model\Collection as ModelCollection; use think\model\Relation; use think\model\relation\OneToOne; use think\Paginator; class Query { - // 数据库Connection对象实例 + /** + * 当前数据库连接对象 + * @var Connection + */ protected $connection; - // 数据库Builder对象实例 - protected $builder; - // 当前模型类名称 + + /** + * 当前模型对象 + * @var Model + */ protected $model; - // 当前数据表名称(含前缀) - protected $table = ''; - // 当前数据表名称(不含前缀) + + /** + * 当前数据表名称(不含前缀) + * @var string + */ protected $name = ''; - // 当前数据表主键 + + /** + * 当前数据表主键 + * @var string|array + */ protected $pk; - // 当前数据表前缀 + + /** + * 当前数据表前缀 + * @var string + */ protected $prefix = ''; - // 查询参数 + + /** + * 当前查询参数 + * @var array + */ protected $options = []; - // 参数绑定 + + /** + * 当前参数绑定 + * @var array + */ protected $bind = []; - // 数据表信息 - protected static $info = []; - // 回调事件 + + /** + * 事件回调 + * @var array + */ private static $event = []; - // 读取主库 + + /** + * 扩展查询方法 + * @var array + */ + private static $extend = []; + + /** + * 读取主库的表 + * @var array + */ protected static $readMaster = []; /** - * 构造函数 - * @access public - * @param Connection $connection 数据库对象实例 - * @param Model $model 模型对象 + * 日期查询表达式 + * @var array */ - public function __construct(Connection $connection = null, $model = null) + protected $timeRule = [ + 'today' => ['today', 'tomorrow -1second'], + 'yesterday' => ['yesterday', 'today -1second'], + 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'], + 'year' => ['this year 1/1', 'next year 1/1 -1second'], + 'last year' => ['last year 1/1', 'this year 1/1 -1second'], + ]; + + /** + * 日期查询快捷定义 + * @var array + */ + protected $timeExp = ['d' => 'today', 'w' => 'week', 'm' => 'month', 'y' => 'year']; + + /** + * 架构函数 + * @access public + */ + public function __construct(Connection $connection = null) { - $this->connection = $connection ?: Db::connect([], true); - $this->prefix = $this->connection->getConfig('prefix'); - $this->model = $model; - // 设置当前连接的Builder对象 - $this->setBuilder(); + if (is_null($connection)) { + $this->connection = Db::connect(); + } else { + $this->connection = $connection; + } + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 创建一个新的查询对象 + * @access public + * @return Query + */ + public function newQuery() + { + $query = new static($this->connection); + + if ($this->model) { + $query->model($this->model); + } + + if (isset($this->options['table'])) { + $query->table($this->options['table']); + } else { + $query->name($this->name); + } + + if (isset($this->options['json'])) { + $query->json($this->options['json'], $this->options['json_assoc']); + } + + if (isset($this->options['field_type'])) { + $query->setJsonFieldType($this->options['field_type']); + } + + return $query; } /** * 利用__call方法实现一些特殊的Model方法 * @access public - * @param string $method 方法名称 - * @param array $args 调用参数 + * @param string $method 方法名称 + * @param array $args 调用参数 * @return mixed * @throws DbException * @throws Exception */ public function __call($method, $args) { - if (strtolower(substr($method, 0, 5)) == 'getby') { + if (isset(self::$extend[strtolower($method)])) { + // 调用扩展查询方法 + array_unshift($args, $this); + + return Container::getInstance() + ->invoke(self::$extend[strtolower($method)], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'getby') { // 根据某个字段获取记录 - $field = Loader::parseName(substr($method, 5)); - $where[$field] = $args[0]; - return $this->where($where)->find(); + $field = Loader::parseName(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { // 根据某个字段获取记录的某个值 - $name = Loader::parseName(substr($method, 10)); - $where[$name] = $args[0]; - return $this->where($where)->value($args[1]); + $name = Loader::parseName(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Loader::parseName(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Loader::parseName(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { // 动态调用命名范围 $method = 'scope' . $method; @@ -100,10 +197,42 @@ class Query call_user_func_array([$this->model, $method], $args); return $this; } else { - throw new Exception('method not exist:' . __CLASS__ . '->' . $method); + throw new Exception('method not exist:' . ($this->model ? get_class($this->model) : static::class) . '->' . $method); } } + /** + * 扩展查询方法 + * @access public + * @param string|array $method 查询方法名 + * @param callable $callback + * @return void + */ + public static function extend($method, $callback = null) + { + if (is_array($method)) { + foreach ($method as $key => $val) { + self::$extend[strtolower($key)] = $val; + } + } else { + self::$extend[strtolower($method)] = $callback; + } + } + + /** + * 设置当前的数据库Connection对象 + * @access public + * @param Connection $connection + * @return $this + */ + public function setConnection(Connection $connection) + { + $this->connection = $connection; + $this->prefix = $this->connection->getConfig('prefix'); + + return $this; + } + /** * 获取当前的数据库Connection对象 * @access public @@ -115,53 +244,36 @@ class Query } /** - * 切换当前的数据库连接 + * 指定模型 * @access public - * @param mixed $config + * @param Model $model 模型对象实例 * @return $this */ - public function connect($config) + public function model(Model $model) { - $this->connection = Db::connect($config); - $this->setBuilder(); - $this->prefix = $this->connection->getConfig('prefix'); + $this->model = $model; return $this; } /** - * 设置当前的数据库Builder对象 - * @access protected - * @return void - */ - protected function setBuilder() - { - $class = $this->connection->getBuilder(); - $this->builder = new $class($this->connection, $this); - } - - /** - * 获取当前的模型对象实例 + * 获取当前的模型对象 * @access public * @return Model|null */ public function getModel() { - return $this->model; + return $this->model ? $this->model->setQuery($this) : null; } /** - * 设置后续从主库读取数据 + * 设置从主库读取数据 * @access public - * @param bool $allTable - * @return void + * @param bool $all 是否所有表有效 + * @return $this */ - public function readMaster($allTable = false) + public function readMaster($all = false) { - if ($allTable) { - $table = '*'; - } else { - $table = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); - } + $table = $all ? '*' : $this->getTable(); static::$readMaster[$table] = true; @@ -169,19 +281,9 @@ class Query } /** - * 获取当前的builder实例对象 + * 指定当前数据表名(不含前缀) * @access public - * @return Builder - */ - public function getBuilder() - { - return $this->builder; - } - - /** - * 指定默认的数据表名(不含前缀) - * @access public - * @param string $name + * @param string $name * @return $this */ public function name($name) @@ -191,75 +293,53 @@ class Query } /** - * 指定默认数据表名(含前缀) + * 获取当前的数据表名称 * @access public - * @param string $table 表名 - * @return $this + * @return string */ - public function setTable($table) + public function getName() { - $this->table = $table; - return $this; + return $this->name ?: $this->model->getName(); } /** * 得到当前或者指定名称的数据表 * @access public - * @param string $name + * @param string $name * @return string */ public function getTable($name = '') { - if ($name || empty($this->table)) { - $name = $name ?: $this->name; - $tableName = $this->prefix; - if ($name) { - $tableName .= Loader::parseName($name); - } - } else { - $tableName = $this->table; + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; } - return $tableName; - } - /** - * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) - * @access public - * @param string $sql sql语句 - * @return string - */ - public function parseSqlTable($sql) - { - if (false !== strpos($sql, '__')) { - $prefix = $this->prefix; - $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { - return $prefix . strtolower($match[1]); - }, $sql); - } - return $sql; + $name = $name ?: $this->name; + + return $this->prefix . Loader::parseName($name); } /** * 执行查询 返回数据集 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param boolean $master 是否在主服务器读操作 - * @param bool|string $class 指定返回的数据集对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 * @return mixed * @throws BindParamException * @throws PDOException */ - public function query($sql, $bind = [], $master = false, $class = false) + public function query($sql, $bind = [], $master = false, $pdo = false) { - return $this->connection->query($sql, $bind, $master, $class); + return $this->connection->query($sql, $bind, $master, $pdo); } /** * 执行语句 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 + * @param string $sql sql指令 + * @param array $bind 参数绑定 * @return int * @throws BindParamException * @throws PDOException @@ -269,10 +349,21 @@ class Query return $this->connection->execute($sql, $bind, $this); } + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + $this->connection->listen($callback); + } + /** * 获取最近插入的ID * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return string */ public function getLastInsID($sequence = null) @@ -280,6 +371,16 @@ class Query return $this->connection->getLastInsID($sequence); } + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->connection->getNumRows(); + } + /** * 获取最近一次查询的sql语句 * @access public @@ -290,10 +391,66 @@ class Query return $this->connection->getLastSql(); } + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa($callback, array $dbs = []) + { + $xid = uniqid('xa'); + + if (empty($dbs)) { + $dbs[] = $this->getConnection(); + } + + foreach ($dbs as $key => $db) { + if ($db instanceof Query) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransXa($xid); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + foreach ($dbs as $db) { + $db->prepareXa($xid); + } + + foreach ($dbs as $db) { + $db->commitXa($xid); + } + + return $result; + } catch (\Exception $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } catch (\Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } + } + /** * 执行数据库事务 * @access public - * @param callable $callback 数据操作方法回调 + * @param callable $callback 数据操作方法回调 * @return mixed */ public function transaction($callback) @@ -337,32 +494,63 @@ class Query * 批处理执行SQL语句 * 批处理的指令都认为是execute操作 * @access public - * @param array $sql SQL批处理指令 + * @param array $sql SQL批处理指令 * @return boolean */ - public function batchQuery($sql = [], $bind = []) + public function batchQuery($sql = []) { - return $this->connection->batchQuery($sql, $bind); + return $this->connection->batchQuery($sql); } /** * 获取数据库的配置参数 * @access public - * @param string $name 参数名称 - * @return boolean + * @param string $name 参数名称 + * @return mixed */ public function getConfig($name = '') { return $this->connection->getConfig($name); } + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = '') + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName = '', $field = null) + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getFieldsType($tableName, $field); + } + /** * 得到分表的的数据表名 * @access public - * @param array $data 操作的数据 - * @param string $field 分表依据的字段 - * @param array $rule 分表规则 - * @return string + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return array */ public function getPartitionTableName($data, $field, $rule = []) { @@ -394,195 +582,110 @@ class Query default: if (function_exists($type)) { // 支持指定函数哈希 - $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1; - } else { - // 按照字段的首字母的值分表 - $seq = (ord($value{0}) % $rule['num']) + 1; + $value = $type($value); } - } - return $this->getTable() . '_' . $seq; - } else { - // 当设置的分表字段不在查询条件或者数据中 - // 进行联合查询,必须设定 partition['num'] - $tableName = []; - for ($i = 0; $i < $rule['num']; $i++) { - $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + + $seq = (ord(substr($value, 0, 1)) % $rule['num']) + 1; } - $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; - return $tableName; + return $this->getTable() . '_' . $seq; } + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + return ['( ' . implode(" UNION ", $tableName) . ' )' => $this->name]; } /** * 得到某个字段的值 * @access public - * @param string $field 字段名 - * @param mixed $default 默认值 - * @param bool $force 强制转为数字类型 + * @param string $field 字段名 + * @param mixed $default 默认值 * @return mixed */ - public function value($field, $default = null, $force = false) + public function value($field, $default = null) { - $result = false; - if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { - // 判断查询缓存 - $cache = $this->options['cache']; - if (empty($this->options['table'])) { - $this->options['table'] = $this->getTable(); - } - $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); - $result = Cache::get($key); - } - if (false === $result) { - if (isset($this->options['field'])) { - unset($this->options['field']); - } - $pdo = $this->field($field)->limit(1)->getPdo(); - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } + $this->parseOptions(); - $result = $pdo->fetchColumn(); - if ($force) { - $result = (float) $result; - } - - if (isset($cache) && false !== $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } - } else { - // 清空查询条件 - $this->options = []; - } - return false !== $result ? $result : $default; + return $this->connection->value($this, $field, $default); } /** * 得到某个列的数组 * @access public - * @param string $field 字段名 多个字段用逗号分隔 - * @param string $key 索引 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 * @return array */ public function column($field, $key = '') { - $result = false; - if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { - // 判断查询缓存 - $cache = $this->options['cache']; - if (empty($this->options['table'])) { - $this->options['table'] = $this->getTable(); - } - $guid = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); - $result = Cache::get($guid); - } - if (false === $result) { - if (isset($this->options['field'])) { - unset($this->options['field']); - } - if (is_null($field)) { - $field = '*'; - } elseif ($key && '*' != $field) { - $field = $key . ',' . $field; - } - $pdo = $this->field($field)->getPdo(); - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } - if (1 == $pdo->columnCount()) { - $result = $pdo->fetchAll(PDO::FETCH_COLUMN); - } else { - $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); - if ($resultSet) { - $fields = array_keys($resultSet[0]); - $count = count($fields); - $key1 = array_shift($fields); - $key2 = $fields ? array_shift($fields) : ''; - $key = $key ?: $key1; - if (strpos($key, '.')) { - list($alias, $key) = explode('.', $key); - } - foreach ($resultSet as $val) { - if ($count > 2) { - $result[$val[$key]] = $val; - } elseif (2 == $count) { - $result[$val[$key]] = $val[$key2]; - } elseif (1 == $count) { - $result[$val[$key]] = $val[$key1]; - } - } - } else { - $result = []; - } - } - if (isset($cache) && isset($guid)) { - // 缓存数据 - $this->cacheData($guid, $result, $cache); - } - } else { - // 清空查询条件 - $this->options = []; + $this->parseOptions(); + + return $this->connection->column($this, $field, $key); + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + $this->parseOptions(); + + $result = $this->connection->aggregate($this, $aggregate, $field); + + if (!empty($this->options['fetch_sql'])) { + return $result; + } elseif ($force) { + $result = (float) $result; } + return $result; } /** * COUNT查询 * @access public - * @param string $field 字段名 - * @return integer|string + * @param string|Expression $field 字段名 + * @return float|string */ public function count($field = '*') { - if (isset($this->options['group'])) { - if (!preg_match('/^[\w\.\*]+$/', $field)) { - throw new Exception('not support data:' . $field); - } + if (!empty($this->options['group'])) { // 支持GROUP $options = $this->getOptions(); - $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + $subSql = $this->options($options) + ->field('count(' . $field . ') AS think_count') + ->bind($this->bind) + ->buildSql(); - $count = $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + if (!empty($options['fetch_sql'])) { + $query->fetchSql(true); + } + + $count = $query->aggregate('COUNT', '*', true); } else { $count = $this->aggregate('COUNT', $field, true); } return is_string($count) ? $count : (int) $count; - - } - - /** - * 聚合查询 - * @access public - * @param string $aggregate 聚合方法 - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 - * @return mixed - */ - public function aggregate($aggregate, $field, $force = false) - { - if (0 === stripos($field, 'DISTINCT ')) { - list($distinct, $field) = explode(' ', $field); - } - - if (!preg_match('/^[\w\.\+\-\*]+$/', $field)) { - throw new Exception('not support data:' . $field); - } - - $result = $this->value($aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $field . ') AS tp_' . strtolower($aggregate), 0, $force); - - return $result; } /** * SUM查询 * @access public - * @param string $field 字段名 - * @return float|int + * @param string|Expression $field 字段名 + * @return float */ public function sum($field) { @@ -592,8 +695,8 @@ class Query /** * MIN查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function min($field, $force = true) @@ -604,8 +707,8 @@ class Query /** * MAX查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function max($field, $force = true) @@ -616,8 +719,8 @@ class Query /** * AVG查询 * @access public - * @param string $field 字段名 - * @return float|int + * @param string|Expression $field 字段名 + * @return float */ public function avg($field) { @@ -628,8 +731,8 @@ class Query * 设置记录的某个字段值 * 支持使用数据库字段和方法 * @access public - * @param string|array $field 字段名 - * @param mixed $value 字段值 + * @param string|array $field 字段名 + * @param mixed $value 字段值 * @return integer */ public function setField($field, $value = '') @@ -639,106 +742,122 @@ class Query } else { $data[$field] = $value; } + return $this->update($data); } /** * 字段值(延迟)增长 * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) * @return integer|true * @throws Exception */ public function setInc($field, $step = 1, $lazyTime = 0) { $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { // 没有条件不做任何更新 throw new Exception('no data to update'); } + if ($lazyTime > 0) { // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + if (false === $step) { // 清空查询条件 $this->options = []; return true; } } - return $this->setField($field, ['inc', $step]); + + return $this->setField($field, ['INC', $step]); } /** * 字段值(延迟)减少 * @access public - * @param string $field 字段名 - * @param integer $step 减少值 - * @param integer $lazyTime 延时时间(s) + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) * @return integer|true * @throws Exception */ public function setDec($field, $step = 1, $lazyTime = 0) { $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { // 没有条件不做任何更新 throw new Exception('no data to update'); } + if ($lazyTime > 0) { // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + if (false === $step) { // 清空查询条件 $this->options = []; return true; } - return $this->setField($field, ['inc', $step]); + + $value = ['INC', $step]; + } else { + $value = ['DEC', $step]; } - return $this->setField($field, ['dec', $step]); + + return $this->setField($field, $value); } /** * 延时更新检查 返回false表示需要延时 * 否则返回实际写入的数值 * @access protected - * @param string $type 自增或者自减 - * @param string $guid 写入标识 - * @param integer $step 写入步进值 - * @param integer $lazyTime 延时时间(s) + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) * @return false|integer */ protected function lazyWrite($type, $guid, $step, $lazyTime) { - if (!Cache::has($guid . '_time')) { + $cache = Container::get('cache'); + + if (!$cache->has($guid . '_time')) { // 计时开始 - Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); - Cache::$type($guid, $step); - } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { + $cache->set($guid . '_time', time(), 0); + $cache->$type($guid, $step); + } elseif (time() > $cache->get($guid . '_time') + $lazyTime) { // 删除缓存 - $value = Cache::$type($guid, $step); - Cache::rm($guid); - Cache::rm($guid . '_time'); + $value = $cache->$type($guid, $step); + $cache->rm($guid); + $cache->rm($guid . '_time'); return 0 === $value ? false : $value; } else { // 更新缓存 - Cache::$type($guid, $step); + $cache->$type($guid, $step); } + return false; } /** * 查询SQL组装 join * @access public - * @param mixed $join 关联的表名 - * @param mixed $condition 条件 - * @param string $type JOIN类型 + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 * @return $this */ - public function join($join, $condition = null, $type = 'INNER') + public function join($join, $condition = null, $type = 'INNER', $bind = []) { if (empty($condition)) { // 如果为组数,则循环调用join @@ -749,27 +868,70 @@ class Query } } else { $table = $this->getJoinTable($join); - + if ($bind) { + $this->bindParams($condition, $bind); + } $this->options['join'][] = [$table, strtoupper($type), $condition]; } + return $this; } /** - * 获取Join表名及别名 支持 - * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * LEFT JOIN * @access public - * @param array|string $join - * @return array|string + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function leftJoin($join, $condition = null, $bind = []) + { + return $this->join($join, $condition, 'LEFT'); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function rightJoin($join, $condition = null, $bind = []) + { + return $this->join($join, $condition, 'RIGHT'); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function fullJoin($join, $condition = null, $bind = []) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string $join + * @param string $alias + * @return string */ protected function getJoinTable($join, &$alias = null) { - // 传入的表名为数组 if (is_array($join)) { $table = $join; $alias = array_shift($join); } else { $join = trim($join); + if (false !== strpos($join, '(')) { // 使用子查询 $table = $join; @@ -784,26 +946,33 @@ class Query $alias = $join; } } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { $table = $this->getTable($table); } } + if (isset($alias) && $table != $alias) { $table = [$table => $alias]; } } + return $table; } /** * 查询SQL组装 union * @access public - * @param mixed $union - * @param boolean $all + * @param mixed $union + * @param boolean $all * @return $this */ public function union($union, $all = false) { + if (empty($union)) { + return $this; + } + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; if (is_array($union)) { @@ -811,17 +980,29 @@ class Query } else { $this->options['union'][] = $union; } + return $this; } + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + /** * 指定查询字段 支持字段排除和指定数据表 * @access public - * @param mixed $field - * @param boolean $except 是否排除 - * @param string $tableName 数据表名 - * @param string $prefix 字段前缀 - * @param string $alias 别名前缀 + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 * @return $this */ public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') @@ -837,32 +1018,39 @@ class Query if (preg_match('/[\<\'\"\(]/', $field)) { return $this->fieldRaw($field); } + $field = array_map('trim', explode(',', $field)); } + if (true === $field) { // 获取全部字段 - $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $fields = $this->getTableFields($tableName); $field = $fields ?: ['*']; } elseif ($except) { // 字段排除 - $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $fields = $this->getTableFields($tableName); $field = $fields ? array_diff($fields, $field) : $field; } + if ($tableName) { // 添加统一的前缀 $prefix = $prefix ?: $tableName; - foreach ($field as $key => $val) { - if (is_numeric($key)) { - $val = $prefix . '.' . $val . ($alias ? ' AS ' . $alias . $val : ''); + foreach ($field as $key => &$val) { + if (is_numeric($key) && $alias) { + $field[$prefix . '.' . $val] = $alias . $val; + unset($field[$key]); + } elseif (is_numeric($key)) { + $val = $prefix . '.' . $val; } - $field[$key] = $val; } } if (isset($this->options['field'])) { $field = array_merge((array) $this->options['field'], $field); } + $this->options['field'] = array_unique($field); + return $this; } @@ -870,25 +1058,20 @@ class Query * 表达式方式指定查询字段 * @access public * @param string $field 字段名 - * @param array $bind 参数绑定 * @return $this */ - public function fieldRaw($field, array $bind = []) + public function fieldRaw($field) { $this->options['field'][] = $this->raw($field); - if ($bind) { - $this->bind($bind); - } - return $this; } /** * 设置数据 * @access public - * @param mixed $field 字段名或者数据 - * @param mixed $value 字段值 + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 * @return $this */ public function data($field, $value = null) @@ -898,46 +1081,51 @@ class Query } else { $this->options['data'][$field] = $value; } + return $this; } /** * 字段值增长 * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 + * @param string|array $field 字段名 + * @param integer $step 增长值 * @return $this */ - public function inc($field, $step = 1) + public function inc($field, $step = 1, $op = 'INC') { $fields = is_string($field) ? explode(',', $field) : $field; - foreach ($fields as $field) { - $this->data($field, ['inc', $step]); + + foreach ($fields as $field => $val) { + if (is_numeric($field)) { + $field = $val; + } else { + $step = $val; + } + + $this->data($field, [$op, $step]); } + return $this; } /** * 字段值减少 * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 + * @param string|array $field 字段名 + * @param integer $step 增长值 * @return $this */ public function dec($field, $step = 1) { - $fields = is_string($field) ? explode(',', $field) : $field; - foreach ($fields as $field) { - $this->data($field, ['dec', $step]); - } - return $this; + return $this->inc($field, $step, 'DEC'); } /** * 使用表达式设置数据 * @access public - * @param string $field 字段名 - * @param string $value 字段值 + * @param string $field 字段名 + * @param string $value 字段值 * @return $this */ public function exp($field, $value) @@ -960,15 +1148,16 @@ class Query /** * 指定JOIN查询字段 * @access public - * @param string|array $table 数据表 - * @param string|array $field 查询字段 - * @param mixed $on JOIN条件 - * @param string $type JOIN类型 + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param mixed $on JOIN条件 + * @param string $type JOIN类型 * @return $this */ public function view($join, $field = true, $on = null, $type = 'INNER') { $this->options['view'] = true; + if (is_array($join) && key($join) === 0) { foreach ($join as $key => $val) { $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); @@ -983,9 +1172,11 @@ class Query if (is_string($field)) { $field = explode(',', $field); } + foreach ($field as $key => $val) { if (is_numeric($key)) { - $fields[] = $alias . '.' . $val; + $fields[] = $alias . '.' . $val; + $this->options['map'][$val] = $alias . '.' . $val; } else { if (preg_match('/[,=\.\'\"\(\s]/', $key)) { @@ -993,80 +1184,281 @@ class Query } else { $name = $alias . '.' . $key; } - $fields[$name] = $val; + + $fields[] = $name . ' AS ' . $val; + $this->options['map'][$val] = $name; } } } + $this->field($fields); + if ($on) { $this->join($table, $on, $type); } else { $this->table($table); } } + return $this; } /** * 设置分表规则 * @access public - * @param array $data 操作的数据 - * @param string $field 分表依据的字段 - * @param array $rule 分表规则 + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 * @return $this */ public function partition($data, $field, $rule = []) { $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + return $this; } /** * 指定AND查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 * @return $this */ public function where($field, $op = null, $condition = null) { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('AND', $field, $op, $condition, $param); - return $this; + return $this->parseWhereExp('AND', $field, $op, $condition, $param); } /** * 指定OR查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 * @return $this */ public function whereOr($field, $op = null, $condition = null) { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('OR', $field, $op, $condition, $param); - return $this; + return $this->parseWhereExp('OR', $field, $op, $condition, $param); } /** * 指定XOR查询条件 * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 * @return $this */ public function whereXor($field, $op = null, $condition = null) { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('XOR', $field, $op, $condition, $param); + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); + } + + /** + * 比较两个字段 + * @access public + * @param string|array $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereColumn($field1, $operator = null, $field2 = null, $logic = 'AND') + { + if (is_array($field1)) { + foreach ($field1 as $item) { + $this->whereColumn($item[0], $item[1], isset($item[2]) ? $item[2] : null); + } + return $this; + } + + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); + } + + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition]; + } + + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $where, $bind = [], $logic = 'AND') + { + if ($bind) { + $this->bindParams($where, $bind); + } + + $this->options['where'][$logic][] = [$field, 'EXP', $this->raw($where)]; + return $this; } @@ -1080,15 +1472,39 @@ class Query */ public function whereRaw($where, $bind = [], $logic = 'AND') { - $this->options['where'][$logic][] = $this->raw($where); - if ($bind) { - $this->bind($bind); + $this->bindParams($where, $bind); } + $this->options['where'][$logic][] = $this->raw($where); + return $this; } + /** + * 参数绑定 + * @access public + * @param string $sql 绑定的sql表达式 + * @param array $bind 参数绑定 + * @return void + */ + protected function bindParams(&$sql, array $bind = []) + { + foreach ($bind as $key => $value) { + if (is_array($value)) { + $name = $this->bind($value[0], $value[1], isset($value[2]) ? $value[2] : null); + } else { + $name = $this->bind($value); + } + + if (is_numeric($key)) { + $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, ':' . $name, $sql); + } + } + } + /** * 指定表达式查询条件 OR * @access public @@ -1102,312 +1518,208 @@ class Query } /** - * 指定Null查询条件 - * @access public - * @param mixed $field 查询字段 - * @param string $logic 查询逻辑 and or xor + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 * @return $this */ - public function whereNull($field, $logic = 'AND') + protected function parseWhereExp($logic, $field, $op, $condition, array $param = [], $strict = false) { - $this->parseWhereExp($logic, $field, 'null', null, [], true); - return $this; - } - - /** - * 指定NotNull查询条件 - * @access public - * @param mixed $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotNull($field, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'notnull', null, [], true); - return $this; - } - - /** - * 指定Exists查询条件 - * @access public - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereExists($condition, $logic = 'AND') - { - $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; - return $this; - } - - /** - * 指定NotExists查询条件 - * @access public - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotExists($condition, $logic = 'AND') - { - $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; - return $this; - } - - /** - * 指定In查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereIn($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'in', $condition, [], true); - return $this; - } - - /** - * 指定NotIn查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotIn($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); - return $this; - } - - /** - * 指定Like查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereLike($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'like', $condition, [], true); - return $this; - } - - /** - * 指定NotLike查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotLike($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); - return $this; - } - - /** - * 指定Between查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereBetween($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'between', $condition, [], true); - return $this; - } - - /** - * 指定NotBetween查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotBetween($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); - return $this; - } - - /** - * 指定Exp查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereExp($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'exp', $this->raw($condition), [], true); - return $this; - } - - /** - * 设置软删除字段及条件 - * @access public - * @param false|string $field 查询字段 - * @param mixed $condition 查询条件 - * @return $this - */ - public function useSoftDelete($field, $condition = null) - { - if ($field) { - $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; + if ($field instanceof $this) { + $this->options['where'] = $field->getOptions('where'); + $this->bind($field->getBind(false)); + return $this; } + + $logic = strtoupper($logic); + + if ($field instanceof Where) { + $this->options['where'][$logic] = $field->parse(); + return $this; + } + + if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Expression) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif ($strict) { + // 使用严格模式查询 + $where = [$field, $op, $condition, $logic]; + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof \Closure) { + $where = $field; + } elseif (is_string($field)) { + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, $op, $logic); + } elseif (is_string($op) && strtolower($op) == 'exp') { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; + return $this->whereExp($field, $condition, $bind, $logic); + } + + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + $this->options['where'][$logic][] = $where; + } + return $this; } /** * 分析查询表达式 - * @access public - * @param string $logic 查询逻辑 and or xor - * @param string|array|\Closure $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @param array $param 查询参数 - * @param bool $strict 严格模式 - * @return void + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return mixed */ - protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) + protected function parseWhereItem($logic, $field, $op, $condition, $param = []) { - $logic = strtoupper($logic); - if ($field instanceof \Closure) { - $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; - return; - } - - if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { - $field = $this->options['via'] . '.' . $field; - } - - if ($field instanceof Expression) { - return $this->whereRaw($field, is_array($op) ? $op : []); - } elseif ($strict) { - // 使用严格模式查询 - $where[$field] = [$op, $condition]; - - // 记录一个字段多次查询条件 - $this->options['multi'][$logic][$field][] = $where[$field]; - } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { - $where[] = ['exp', $this->raw($field)]; - if (is_array($op)) { - // 参数绑定 - $this->bind($op); - } - } elseif (is_null($op) && is_null($condition)) { - if (is_array($field)) { - // 数组批量查询 - $where = $field; - foreach ($where as $k => $val) { - $this->options['multi'][$logic][$k][] = $val; - } - } elseif ($field && is_string($field)) { - // 字符串查询 - $where[$field] = ['null', '']; - $this->options['multi'][$logic][$field][] = $where[$field]; - } - } elseif (is_array($op)) { - $where[$field] = $param; - } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { - // null查询 - $where[$field] = [$op, '']; - - $this->options['multi'][$logic][$field][] = $where[$field]; - } elseif (is_null($condition)) { - // 字段相等查询 - $where[$field] = ['eq', $op]; - - $this->options['multi'][$logic][$field][] = $where[$field]; - } else { - if ('exp' == strtolower($op)) { - $where[$field] = ['exp', $this->raw($condition)]; - // 参数绑定 - if (isset($param[2]) && is_array($param[2])) { - $this->bind($param[2]); - } + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } elseif ($field && is_null($condition)) { + if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) { + $where = [$field, 'NULL', '']; + } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) { + $where = [$field, 'NOTNULL', '']; } else { - $where[$field] = [$op, $condition]; + // 字段相等查询 + $where = [$field, '=', $op]; } - // 记录一个字段多次查询条件 - $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition]; + } else { + $where = $field ? [$field, $op, $condition, isset($param[2]) ? $param[2] : null] : null; } - if (!empty($where)) { - if (!isset($this->options['where'][$logic])) { - $this->options['where'][$logic] = []; - } - if (is_string($field) && $this->checkMultiField($field, $logic)) { - $where[$field] = $this->options['multi'][$logic][$field]; - } elseif (is_array($field)) { - foreach ($field as $key => $val) { - if ($this->checkMultiField($key, $logic)) { - $where[$key] = $this->options['multi'][$logic][$key]; - } - } - } - $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); - } + return $where; } /** - * 检查是否存在一个字段多次查询条件 - * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return bool + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this */ - private function checkMultiField($field, $logic) + protected function parseArrayWhereItems($field, $logic) { - return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if ($val instanceof Expression) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } else { + $where[] = [$key, is_array($val) ? 'IN' : '=', $val]; + } + } + } else { + // 数组批量查询 + $where = $field; + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; } /** * 去除某个查询条件 * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function removeWhereField($field, $logic = 'AND') { $logic = strtoupper($logic); - if (isset($this->options['where'][$logic][$field])) { - unset($this->options['where'][$logic][$field]); - unset($this->options['multi'][$logic][$field]); + + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } } + return $this; } /** * 去除查询参数 * @access public - * @param string|bool $option 参数名 true 表示去除所有参数 + * @param string|bool $option 参数名 true 表示去除所有参数 * @return $this */ public function removeOption($option = true) { if (true === $option) { $this->options = []; + $this->bind = []; } elseif (is_string($option) && isset($this->options[$option])) { unset($this->options[$option]); } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param \Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param \Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof \Closure) { + $condition = $condition($this); + } + + if ($condition) { + if ($query instanceof \Closure) { + $query($this, $condition); + } elseif (is_array($query)) { + $this->where($query); + } + } elseif ($otherwise) { + if ($otherwise instanceof \Closure) { + $otherwise($this, $condition); + } elseif (is_array($otherwise)) { + $this->where($otherwise); + } + } + return $this; } /** * 指定查询数量 * @access public - * @param mixed $offset 起始位置 - * @param mixed $length 查询数量 + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 * @return $this */ public function limit($offset, $length = null) @@ -1415,15 +1727,17 @@ class Query if (is_null($length) && strpos($offset, ',')) { list($offset, $length) = explode(',', $offset); } + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + return $this; } /** * 指定分页 * @access public - * @param mixed $page 页数 - * @param mixed $listRows 每页数量 + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 * @return $this */ public function page($page, $listRows = null) @@ -1431,15 +1745,18 @@ class Query if (is_null($listRows) && strpos($page, ',')) { list($page, $listRows) = explode(',', $page); } + $this->options['page'] = [intval($page), intval($listRows)]; + return $this; } /** * 分页查询 - * @param int|array $listRows 每页数量 数组表示配置参数 - * @param int|bool $simple 是否简洁模式或者总记录数 - * @param array $config 配置参数 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 * page:当前页, * path:url路径, * query:url额外参数, @@ -1447,7 +1764,7 @@ class Query * var_page:分页变量, * list_rows:每页数量 * type:分页类名 - * @return \think\Paginator + * @return $this[]|\think\Paginator * @throws DbException */ public function paginate($listRows = null, $simple = false, $config = []) @@ -1456,11 +1773,14 @@ class Query $total = $simple; $simple = false; } + + $paginate = Container::get('config')->pull('paginate'); + if (is_array($listRows)) { - $config = array_merge(Config::get('paginate'), $listRows); + $config = array_merge($paginate, $listRows); $listRows = $config['list_rows']; } else { - $config = array_merge(Config::get('paginate'), $config); + $config = array_merge($paginate, $config); $listRows = $listRows ?: $config['list_rows']; } @@ -1489,13 +1809,17 @@ class Query } else { $results = $this->page($page, $listRows)->select(); } + + $this->removeOption('limit'); + $this->removeOption('page'); + return $class::make($results, $listRows, $page, $total, $simple, $config); } /** * 指定当前操作的数据表 * @access public - * @param mixed $table 表名 + * @param mixed $table 表名 * @return $this */ public function table($table) @@ -1506,6 +1830,7 @@ class Query } elseif (strpos($table, ',')) { $tables = explode(',', $table); $table = []; + foreach ($tables as $item) { list($item, $alias) = explode(' ', trim($item)); if ($alias) { @@ -1524,6 +1849,7 @@ class Query } else { $tables = $table; $table = []; + foreach ($tables as $key => $val) { if (is_numeric($key)) { $table[] = $val; @@ -1533,14 +1859,16 @@ class Query } } } + $this->options['table'] = $table; + return $this; } /** * USING支持 用于多表删除 * @access public - * @param mixed $using + * @param mixed $using * @return $this */ public function using($using) @@ -1552,8 +1880,8 @@ class Query /** * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) * @access public - * @param string|array $field 排序字段 - * @param string $order 排序 + * @param string|array $field 排序字段 + * @param string $order 排序 * @return $this */ public function order($field, $order = null) @@ -1569,6 +1897,7 @@ class Query if (!empty($this->options['via'])) { $field = $this->options['via'] . '.' . $field; } + if (strpos($field, ',')) { $field = array_map('trim', explode(',', $field)); } else { @@ -1584,9 +1913,11 @@ class Query } } } + if (!isset($this->options['order'])) { $this->options['order'] = []; } + if (is_array($field)) { $this->options['order'] = array_merge($this->options['order'], $field); } else { @@ -1603,23 +1934,53 @@ class Query * @param array $bind 参数绑定 * @return $this */ - public function orderRaw($field, array $bind = []) + public function orderRaw($field, $bind = []) { + if ($bind) { + $this->bindParams($field, $bind); + } + $this->options['order'][] = $this->raw($field); - if ($bind) { - $this->bind($bind); + return $this; + } + + /** + * 指定Field排序 order('id',[1,2,3],'desc') + * @access public + * @param string|array $field 排序字段 + * @param array $values 排序值 + * @param string $order + * @return $this + */ + public function orderField($field, array $values, $order = '') + { + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; } return $this; } + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + /** * 查询缓存 * @access public - * @param mixed $key 缓存key - * @param integer|\DateTime $expire 缓存有效期 - * @param string $tag 缓存标签 + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 * @return $this */ public function cache($key = true, $expire = null, $tag = null) @@ -1633,13 +1994,14 @@ class Query if (false !== $key) { $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; } + return $this; } /** * 指定group查询 * @access public - * @param string $group GROUP + * @param string|array $group GROUP * @return $this */ public function group($group) @@ -1651,7 +2013,7 @@ class Query /** * 指定having查询 * @access public - * @param string $having having + * @param string $having having * @return $this */ public function having($having) @@ -1663,20 +2025,21 @@ class Query /** * 指定查询lock * @access public - * @param bool|string $lock 是否lock + * @param bool|string $lock 是否lock * @return $this */ public function lock($lock = false) { $this->options['lock'] = $lock; $this->options['master'] = true; + return $this; } /** * 指定distinct查询 * @access public - * @param string $distinct 是否唯一 + * @param string $distinct 是否唯一 * @return $this */ public function distinct($distinct) @@ -1688,7 +2051,7 @@ class Query /** * 指定数据表别名 * @access public - * @param mixed $alias 数据表别名 + * @param array|string $alias 数据表别名 * @return $this */ public function alias($alias) @@ -1696,7 +2059,7 @@ class Query if (is_array($alias)) { foreach ($alias as $key => $val) { if (false !== strpos($key, '__')) { - $table = $this->parseSqlTable($key); + $table = $this->connection->parseSqlTable($key); } else { $table = $key; } @@ -1706,7 +2069,7 @@ class Query if (isset($this->options['table'])) { $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; if (false !== strpos($table, '__')) { - $table = $this->parseSqlTable($table); + $table = $this->connection->parseSqlTable($table); } } else { $table = $this->getTable(); @@ -1714,13 +2077,14 @@ class Query $this->options['alias'][$table] = $alias; } + return $this; } /** * 指定强制索引 * @access public - * @param string $force 索引名称 + * @param string $force 索引名称 * @return $this */ public function force($force) @@ -1732,7 +2096,7 @@ class Query /** * 查询注释 * @access public - * @param string $comment 注释 + * @param string $comment 注释 * @return $this */ public function comment($comment) @@ -1744,7 +2108,7 @@ class Query /** * 获取执行的SQL语句 * @access public - * @param boolean $fetch 是否返回sql + * @param boolean $fetch 是否返回sql * @return $this */ public function fetchSql($fetch = true) @@ -1756,7 +2120,7 @@ class Query /** * 不主动获取数据集 * @access public - * @param bool $pdo 是否返回 PDOStatement 对象 + * @param bool $pdo 是否返回 PDOStatement 对象 * @return $this */ public function fetchPdo($pdo = true) @@ -1765,6 +2129,19 @@ class Query return $this; } + /** + * 设置是否返回数据集对象(支持设置数据集对象类名) + * @access public + * @param bool|string $collection 是否返回数据集对象 + * @return $this + */ + public function fetchCollection($collection = true) + { + $this->options['collection'] = $collection; + + return $this; + } + /** * 设置从主服务器读取数据 * @access public @@ -1779,7 +2156,7 @@ class Query /** * 设置是否严格检查字段名 * @access public - * @param bool $strict 是否严格检查字段 + * @param bool $strict 是否严格检查字段 * @return $this */ public function strict($strict = true) @@ -1791,7 +2168,7 @@ class Query /** * 设置查询数据不存在是否抛出异常 * @access public - * @param bool $fail 数据不存在是否抛出异常 + * @param bool $fail 数据不存在是否抛出异常 * @return $this */ public function failException($fail = true) @@ -1803,7 +2180,7 @@ class Query /** * 设置自增序列名 * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return $this */ public function sequence($sequence = null) @@ -1812,10 +2189,179 @@ class Query return $this; } + /** + * 设置需要隐藏的输出属性 + * @access public + * @param mixed $hidden 需要隐藏的字段名 + * @return $this + */ + public function hidden($hidden) + { + if ($this->model) { + $this->options['hidden'] = $hidden; + return $this; + } + + return $this->field($hidden, true); + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible 需要输出的属性 + * @return $this + */ + public function visible(array $visible) + { + $this->options['visible'] = $visible; + return $this; + } + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append = []) + { + $this->options['append'] = $append; + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + if (is_array($name)) { + $this->options['with_attr'] = $name; + } else { + $this->options['with_attr'][$name] = $callback; + } + + return $this; + } + + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 + * @return $this + */ + public function json(array $json = [], $assoc = false) + { + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; + return $this; + } + + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setJsonFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getJsonFieldType($field) + { + return isset($this->options['field_type'][$field]) ? $this->options['field_type'][$field] : null; + } + + /** + * 是否允许返回空数据(或空模型) + * @access public + * @param bool $allowEmpty 是否允许为空 + * @return $this + */ + public function allowEmpty($allowEmpty = true) + { + $this->options['allow_empty'] = $allowEmpty; + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|\Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof \Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + + /** + * 使用搜索器条件搜索字段 + * @access public + * @param array $fields 搜索字段 + * @param array $data 搜索数据 + * @param string $prefix 字段前缀标识 + * @return $this + */ + public function withSearch(array $fields, array $data = [], $prefix = '') + { + foreach ($fields as $key => $field) { + if ($field instanceof \Closure) { + $field($this, isset($data[$key]) ? $data[$key] : null, $data, $prefix); + } elseif ($this->model) { + // 检测搜索器 + $fieldName = is_numeric($key) ? $field : $key; + $method = 'search' . Loader::parseName($fieldName, 1) . 'Attr'; + + if (method_exists($this->model, $method)) { + $this->model->$method($this, isset($data[$field]) ? $data[$field] : null, $data, $prefix); + } + } + } + + return $this; + } + /** * 指定数据表主键 * @access public - * @param string $pk 主键 + * @param string $pk 主键 * @return $this */ public function pk($pk) @@ -1827,124 +2373,97 @@ class Query /** * 查询日期或者时间 * @access public - * @param string $field 日期字段名 - * @param string|array $op 比较运算符或者表达式 - * @param string|array $range 比较范围 + * @param string $name 时间表达式 + * @param string|array $rule 时间范围 * @return $this */ - public function whereTime($field, $op, $range = null) + public function timeRule($name, $rule) + { + $this->timeRule[$name] = $rule; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @param string $logic AND OR + * @return $this + */ + public function whereTime($field, $op, $range = null, $logic = 'AND') { if (is_null($range)) { if (is_array($op)) { $range = $op; } else { - // 使用日期表达式 - switch (strtolower($op)) { - case 'today': - case 'd': - $range = ['today', 'tomorrow']; - break; - case 'week': - case 'w': - $range = ['this week 00:00:00', 'next week 00:00:00']; - break; - case 'month': - case 'm': - $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; - break; - case 'year': - case 'y': - $range = ['this year 1/1', 'next year 1/1']; - break; - case 'yesterday': - $range = ['yesterday', 'today']; - break; - case 'last week': - $range = ['last week 00:00:00', 'this week 00:00:00']; - break; - case 'last month': - $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; - break; - case 'last year': - $range = ['last year 1/1', 'this year 1/1']; - break; - default: - $range = $op; + if (isset($this->timeExp[strtolower($op)])) { + $op = $this->timeExp[strtolower($op)]; + } + + if (isset($this->timeRule[strtolower($op)])) { + $range = $this->timeRule[strtolower($op)]; + } else { + $range = $op; } } - $op = is_array($range) ? 'between' : '>'; + + $op = is_array($range) ? 'between' : '>='; } - $this->where($field, strtolower($op) . ' time', $range); - return $this; + + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); } /** - * 获取数据表信息 + * 查询当前时间在两个时间字段范围 * @access public - * @param mixed $tableName 数据表名 留空自动获取 - * @param string $fetch 获取信息类型 包括 fields type bind pk - * @return mixed + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this */ - public function getTableInfo($tableName = '', $fetch = '') + public function whereBetweenTimeField($startField, $endField) { - if (!$tableName) { - $tableName = $this->getTable(); - } - if (is_array($tableName)) { - $tableName = key($tableName) ?: current($tableName); + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField($startField, $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + + /** + * 查询日期或者时间范围 + * @access public + * @param string $field 日期字段名 + * @param string $startTime 开始时间 + * @param string $endTime 结束时间 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTime($field, $startTime, $endTime = null, $logic = 'AND') + { + if (is_null($endTime)) { + $time = is_string($startTime) ? strtotime($startTime) : $startTime; + $endTime = strtotime('+1 day', $time); } - if (strpos($tableName, ',')) { - // 多表不获取字段信息 - return false; - } else { - $tableName = $this->parseSqlTable($tableName); - } - - // 修正子查询作为表名的问题 - if (strpos($tableName, ')')) { - return []; - } - - list($guid) = explode(' ', $tableName); - $db = $this->getConfig('database'); - if (!isset(self::$info[$db . '.' . $guid])) { - if (!strpos($guid, '.')) { - $schema = $db . '.' . $guid; - } else { - $schema = $guid; - } - // 读取缓存 - if (!App::$debug && is_file(RUNTIME_PATH . 'schema/' . $schema . '.php')) { - $info = include RUNTIME_PATH . 'schema/' . $schema . '.php'; - } else { - $info = $this->connection->getFields($guid); - } - $fields = array_keys($info); - $bind = $type = []; - foreach ($info as $key => $val) { - // 记录字段类型 - $type[$key] = $val['type']; - $bind[$key] = $this->getFieldBindType($val['type']); - if (!empty($val['primary'])) { - $pk[] = $key; - } - } - if (isset($pk)) { - // 设置主键 - $pk = count($pk) > 1 ? $pk : $pk[0]; - } else { - $pk = null; - } - self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; - } - return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; + return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true); } /** * 获取当前数据表的主键 * @access public - * @param string|array $options 数据表名或者查询参数 + * @param string|array $options 数据表名或者查询参数 * @return string|array */ public function getPk($options = '') @@ -1952,78 +2471,38 @@ class Query if (!empty($this->pk)) { $pk = $this->pk; } else { - $pk = $this->getTableInfo(is_array($options) && isset($options['table']) ? $options['table'] : $options, 'pk'); + $pk = $this->connection->getPk(is_array($options) && isset($options['table']) ? $options['table'] : $this->getTable()); } + return $pk; } - // 获取当前数据表字段信息 - public function getTableFields($table = '') - { - return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); - } - - // 获取当前数据表字段类型 - public function getFieldsType($table = '') - { - return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); - } - - // 获取当前数据表绑定信息 - public function getFieldsBind($table = '') - { - $types = $this->getFieldsType($table); - $bind = []; - if ($types) { - foreach ($types as $key => $type) { - $bind[$key] = $this->getFieldBindType($type); - } - } - return $bind; - } - - /** - * 获取字段绑定类型 - * @access public - * @param string $type 字段类型 - * @return integer - */ - protected function getFieldBindType($type) - { - if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { - $bind = PDO::PARAM_STR; - } elseif (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { - $bind = PDO::PARAM_INT; - } elseif (preg_match('/bool/is', $type)) { - $bind = PDO::PARAM_BOOL; - } else { - $bind = PDO::PARAM_STR; - } - return $bind; - } - /** * 参数绑定 * @access public - * @param mixed $key 参数名 - * @param mixed $value 绑定变量值 - * @param integer $type 绑定类型 - * @return $this + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @param string $name 绑定名称 + * @return $this|string */ - public function bind($key, $value = false, $type = PDO::PARAM_STR) + public function bind($value, $type = PDO::PARAM_STR, $name = null) { - if (is_array($key)) { - $this->bind = array_merge($this->bind, $key); + if (is_array($value)) { + $this->bind = array_merge($this->bind, $value); } else { - $this->bind[$key] = [$value, $type]; + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_'; + + $this->bind[$name] = [$value, $type]; + return $name; } + return $this; } /** * 检测参数是否已经绑定 * @access public - * @param string $key 参数名 + * @param string $key 参数名 * @return bool */ public function isBind($key) @@ -2031,10 +2510,23 @@ class Query return isset($this->bind[$key]); } + /** + * 查询参数赋值 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value) + { + $this->options[$name] = $value; + return $this; + } + /** * 查询参数赋值 * @access protected - * @param array $options 表达式参数 + * @param array $options 表达式参数 * @return $this */ protected function options(array $options) @@ -2046,22 +2538,34 @@ class Query /** * 获取当前的查询参数 * @access public - * @param string $name 参数名 + * @param string $name 参数名 * @return mixed */ public function getOptions($name = '') { if ('' === $name) { return $this->options; - } else { - return isset($this->options[$name]) ? $this->options[$name] : null; } + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption($option, $value) + { + $this->options[$option] = $value; + return $this; } /** * 设置关联查询JOIN预查询 * @access public - * @param string|array $with 关联方法名称 + * @param string|array $with 关联方法名称 * @return $this */ public function with($with) @@ -2079,77 +2583,202 @@ class Query /** @var Model $class */ $class = $this->model; foreach ($with as $key => $relation) { - $subRelation = ''; - $closure = false; + $closure = null; + if ($relation instanceof \Closure) { // 支持闭包查询过滤关联条件 - $closure = $relation; - $relation = $key; - $with[$key] = $key; + $closure = $relation; + $relation = $key; } elseif (is_array($relation)) { - $subRelation = $relation; - $relation = $key; + $relation = $key; } elseif (is_string($relation) && strpos($relation, '.')) { - $with[$key] = $relation; list($relation, $subRelation) = explode('.', $relation, 2); } /** @var Relation $model */ $relation = Loader::parseName($relation, 1, false); $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { - $model->eagerly($this, $relation, $subRelation, $closure, $first); + $table = $model->getTable(); + $model->removeOption() + ->table($table) + ->eagerly($this, $relation, true, '', $closure, $first); $first = false; - } elseif ($closure) { - $with[$key] = $closure; } } + $this->via(); - if (isset($this->options['with'])) { - $this->options['with'] = array_merge($this->options['with'], $with); - } else { - $this->options['with'] = $with; + + $this->options['with'] = $with; + + return $this; + } + + /** + * 关联预载入 JOIN方式(不支持嵌套) + * @access protected + * @param string|array $with 关联方法名 + * @param string $joinType JOIN方式 + * @return $this + */ + public function withJoin($with, $joinType = '') + { + if (empty($with)) { + return $this; } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $closure = null; + $field = true; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $field = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + + if ($model instanceof OneToOne) { + $model->eagerly($this, $relation, $field, $joinType, $closure, $first); + $first = false; + } else { + // 不支持其它关联 + unset($with[$key]); + } + } + + $this->via(); + + $this->options['with_join'] = $with; + + return $this; + } + + /** + * 关联统计 + * @access protected + * @param string|array $relation 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + protected function withAggregate($relation, $aggregate = 'count', $field = '*', $subQuery = true) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + if (!$subQuery) { + $this->options['with_count'][] = [$relations, $aggregate, $field]; + } else { + if (!isset($this->options['field'])) { + $this->field('*'); + } + + foreach ($relations as $key => $relation) { + $closure = $aggregateField = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (!is_int($key)) { + $aggregateField = $relation; + $relation = $key; + } + + $relation = Loader::parseName($relation, 1, false); + + $count = $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField); + + if (empty($aggregateField)) { + $aggregateField = Loader::parseName($relation) . '_' . $aggregate; + } + + $this->field(['(' . $count . ')' => $aggregateField]); + } + } + return $this; } /** * 关联统计 * @access public - * @param string|array $relation 关联方法名 - * @param bool $subQuery 是否使用子查询 + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 * @return $this */ public function withCount($relation, $subQuery = true) { - if (!$subQuery) { - $this->options['with_count'] = $relation; - } else { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - if (!isset($this->options['field'])) { - $this->field('*'); - } - foreach ($relations as $key => $relation) { - $closure = $name = null; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } elseif (!is_int($key)) { - $name = $relation; - $relation = $key; - } - $relation = Loader::parseName($relation, 1, false); + return $this->withAggregate($relation, 'count', '*', $subQuery); + } - $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $name) . ')'; + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } - if (empty($name)) { - $name = Loader::parseName($relation) . '_count'; - } + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } - $this->field([$count => $name]); - } - } - return $this; + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); } /** @@ -2159,31 +2788,34 @@ class Query * $query->withField("id,name"); * }]) * - * @param string | array $field 指定获取的字段 + * @access public + * @param string | array $field 指定获取的字段 * @return $this */ public function withField($field) { $this->options['with_field'] = $field; + return $this; } /** * 设置当前字段添加的表别名 * @access public - * @param string $via + * @param string $via * @return $this */ public function via($via = '') { $this->options['via'] = $via; + return $this; } /** * 设置关联查询 * @access public - * @param string|array $relation 关联名称 + * @param string|array $relation 关联名称 * @return $this */ public function relation($relation) @@ -2191,113 +2823,44 @@ class Query if (empty($relation)) { return $this; } + if (is_string($relation)) { $relation = explode(',', $relation); } + if (isset($this->options['relation'])) { $this->options['relation'] = array_merge($this->options['relation'], $relation); } else { $this->options['relation'] = $relation; } + return $this; } - /** - * 把主键值转换为查询条件 支持复合主键 - * @access public - * @param array|string $data 主键数据 - * @param mixed $options 表达式参数 - * @return void - * @throws Exception - */ - protected function parsePkWhere($data, &$options) - { - $pk = $this->getPk($options); - // 获取当前数据表 - $table = is_array($options['table']) ? key($options['table']) : $options['table']; - if (!empty($options['alias'][$table])) { - $alias = $options['alias'][$table]; - } - if (is_string($pk)) { - $key = isset($alias) ? $alias . '.' . $pk : $pk; - // 根据主键查询 - if (is_array($data)) { - $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; - } else { - $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; - } - } elseif (is_array($pk) && is_array($data) && !empty($data)) { - // 根据复合主键查询 - foreach ($pk as $key) { - if (isset($data[$key])) { - $attr = isset($alias) ? $alias . '.' . $key : $key; - $where[$attr] = $data[$key]; - } else { - throw new Exception('miss complex primary data'); - } - } - } - - if (!empty($where)) { - if (isset($options['where']['AND'])) { - $options['where']['AND'] = array_merge($options['where']['AND'], $where); - } else { - $options['where']['AND'] = $where; - } - } - return; - } - /** * 插入记录 * @access public - * @param mixed $data 数据 - * @param boolean $replace 是否replace - * @param boolean $getLastInsID 返回自增主键 - * @param string $sequence 自增序列名 + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 * @return integer|string */ public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) { - // 分析查询表达式 - $options = $this->parseExpress(); - $data = array_merge($options['data'], $data); - // 生成SQL语句 - $sql = $this->builder->insert($data, $options, $replace); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } + $this->parseOptions(); - // 执行操作 - $result = 0 === $sql ? 0 : $this->execute($sql, $bind, $this); - if ($result) { - $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); - $lastInsId = $this->getLastInsID($sequence); - if ($lastInsId) { - $pk = $this->getPk($options); - if (is_string($pk)) { - $data[$pk] = $lastInsId; - } - } - $options['data'] = $data; - $this->trigger('after_insert', $options); + $this->options['data'] = array_merge($this->options['data'], $data); - if ($getLastInsID) { - return $lastInsId; - } - } - return $result; + return $this->connection->insert($this, $replace, $getLastInsID, $sequence); } /** * 插入记录并获取自增ID * @access public - * @param mixed $data 数据 - * @param boolean $replace 是否replace - * @param string $sequence 自增序列名 + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 * @return integer|string */ public function insertGetId(array $data, $replace = false, $sequence = null) @@ -2308,145 +2871,89 @@ class Query /** * 批量插入记录 * @access public - * @param mixed $dataSet 数据集 - * @param boolean $replace 是否replace - * @param integer $limit 每次写入数据限制 + * @param array $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 * @return integer|string */ - public function insertAll(array $dataSet, $replace = false, $limit = null) + public function insertAll(array $dataSet = [], $replace = false, $limit = null) { - // 分析查询表达式 - $options = $this->parseExpress(); - if (!is_array(reset($dataSet))) { - return false; + $this->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $this->options['data']; } - // 生成SQL语句 - if (is_null($limit)) { - $sql = $this->builder->insertAll($dataSet, $options, $replace); - } else { - $array = array_chunk($dataSet, $limit, true); - foreach ($array as $item) { - $sql[] = $this->builder->insertAll($item, $options, $replace); - } + if (empty($limit) && !empty($this->options['limit'])) { + $limit = $this->options['limit']; } - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } elseif (is_array($sql)) { - // 执行操作 - return $this->batchQuery($sql, $bind, $this); - } else { - // 执行操作 - return $this->execute($sql, $bind, $this); - } + return $this->connection->insertAll($this, $dataSet, $replace, $limit); } /** * 通过Select方式插入记录 * @access public - * @param string $fields 要插入的数据表字段名 - * @param string $table 要插入的数据表名 + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 * @return integer|string * @throws PDOException */ public function selectInsert($fields, $table) { - // 分析查询表达式 - $options = $this->parseExpress(); - // 生成SQL语句 - $table = $this->parseSqlTable($table); - $sql = $this->builder->selectInsert($fields, $table, $options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } else { - // 执行操作 - return $this->execute($sql, $bind, $this); - } + $this->parseOptions(); + + return $this->connection->selectInsert($this, $fields, $table); } /** * 更新记录 * @access public - * @param mixed $data 数据 + * @param mixed $data 数据 * @return integer|string * @throws Exception * @throws PDOException */ public function update(array $data = []) { - $options = $this->parseExpress(); - $data = array_merge($options['data'], $data); - $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache']['key'])) { - $key = $options['cache']['key']; + $this->parseOptions(); + + $this->options['data'] = array_merge($this->options['data'], $data); + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + $this->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); } - if (empty($options['where'])) { - // 如果存在主键数据 则自动作为更新条件 - if (is_string($pk) && isset($data[$pk])) { - $where[$pk] = $data[$pk]; - if (!isset($key)) { - $key = 'think:' . $options['table'] . '|' . $data[$pk]; - } - unset($data[$pk]); - } elseif (is_array($pk)) { - // 增加复合主键支持 - foreach ($pk as $field) { - if (isset($data[$field])) { - $where[$field] = $data[$field]; - } else { - // 如果缺少复合主键数据则不执行 - throw new Exception('miss complex primary data'); - } - unset($data[$field]); - } + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + if ($condition) { + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); } - if (!isset($where)) { - // 如果没有任何更新条件则不执行 - throw new Exception('miss update condition'); - } else { - $options['where']['AND'] = $where; - } - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); } - // 生成UPDATE SQL语句 - $sql = $this->builder->update($data, $options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } else { - // 检测缓存 - if (isset($key) && Cache::get($key)) { - // 删除缓存 - Cache::rm($key); - } elseif (!empty($options['cache']['tag'])) { - Cache::clear($options['cache']['tag']); - } - // 执行操作 - $result = '' == $sql ? 0 : $this->execute($sql, $bind, $this); - if ($result) { - if (is_string($pk) && isset($where[$pk])) { - $data[$pk] = $where[$pk]; - } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $data[$pk] = $val; - } - $options['data'] = $data; - $this->trigger('after_update', $options); - } - return $result; - } + $this->options['data'] = $data; + + return $this->connection->delete($this); } /** @@ -2456,25 +2963,43 @@ class Query */ public function getPdo() { - // 分析查询表达式 - $options = $this->parseExpress(); - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); + $this->parseOptions(); + + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return \Generator + */ + public function cursor($data = null) + { + if ($data instanceof \Closure) { + $data($this); + $data = null; } - // 执行查询操作 - return $this->query($sql, $bind, $options['master'], true); + + $this->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); } /** * 查找记录 * @access public - * @param array|string|Query|\Closure $data - * @return Collection|false|\PDOStatement|string + * @param array|string|Query|\Closure $data + * @return Collection|array|\PDOStatement|string * @throws DbException * @throws ModelNotFoundException * @throws DataNotFoundException @@ -2484,144 +3009,124 @@ class Query if ($data instanceof Query) { return $data->select(); } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); + $data($this); $data = null; } - // 分析查询表达式 - $options = $this->parseExpress(); + + $this->parseOptions(); if (false === $data) { // 用于子查询 不查询只返回SQL - $options['fetch_sql'] = true; + $this->options['fetch_sql'] = true; } elseif (!is_null($data)) { // 主键条件分析 - $this->parsePkWhere($data, $options); + $this->parsePkWhere($data); } - $resultSet = false; - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - unset($options['cache']); - $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); - $resultSet = Cache::get($key); + $this->options['data'] = $data; + + $resultSet = $this->connection->select($this); + + if ($this->options['fetch_sql']) { + return $resultSet; } - if (false === $resultSet) { - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - $options['data'] = $data; - if ($resultSet = $this->trigger('before_select', $options)) { - } else { - // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - } - - if (isset($cache) && false !== $resultSet) { - // 缓存数据集 - $this->cacheData($key, $resultSet, $cache); - } + // 返回结果处理 + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($this->options); } // 数据列表读取后的处理 if (!empty($this->model)) { // 生成模型对象 - if (count($resultSet) > 0) { - foreach ($resultSet as $key => $result) { - /** @var Model $model */ - $model = $this->model->newInstance($result); - $model->isUpdate(true); + $resultSet = $this->resultSetToModelCollection($resultSet); + } else { + $this->resultSet($resultSet); + } - // 关联查询 - if (!empty($options['relation'])) { - $model->relationQuery($options['relation']); - } - // 关联统计 - if (!empty($options['with_count'])) { - $model->relationCount($model, $options['with_count']); - } - $resultSet[$key] = $model; - } - if (!empty($options['with'])) { - // 预载入 - $model->eagerlyResultSet($resultSet, $options['with']); - } - // 模型数据集转换 - $resultSet = $model->toCollection($resultSet); - } else { - $resultSet = $this->model->toCollection($resultSet); - } - } elseif ('collection' == $this->connection->getConfig('resultset_type')) { - // 返回Collection对象 - $resultSet = new Collection($resultSet); - } - // 返回结果处理 - if (!empty($options['fail']) && count($resultSet) == 0) { - $this->throwNotFound($options); - } return $resultSet; } /** - * 缓存数据 - * @access public - * @param string $key 缓存标识 - * @param mixed $data 缓存数据 - * @param array $config 缓存参数 + * 查询数据转换为模型数据集对象 + * @access protected + * @param array $resultSet 数据集 + * @return ModelCollection */ - protected function cacheData($key, $data, $config = []) + protected function resultSetToModelCollection(array $resultSet) { - if (isset($config['tag'])) { - Cache::tag($config['tag'])->set($key, $data, $config['expire']); - } else { - Cache::set($key, $data, $config['expire']); + if (!empty($this->options['collection']) && is_string($this->options['collection'])) { + $collection = $this->options['collection']; } + + if (empty($resultSet)) { + return $this->model->toCollection([], isset($collection) ? $collection : null); + } + + // 检查动态获取器 + if (!empty($this->options['with_attr'])) { + foreach ($this->options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + list($relation, $field) = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($this->options['with_attr'][$name]); + } + } + } + + $withRelationAttr = isset($withRelationAttr) ? $withRelationAttr : []; + + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result, $this->options, true, $withRelationAttr); + } + + if (!empty($this->options['with'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr); + } + + if (!empty($this->options['with_join'])) { + // JOIN预载入 + $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true); + } + + // 模型数据集转换 + return $result->toCollection($resultSet, isset($collection) ? $collection : null); } /** - * 生成缓存标识 + * 处理数据集 * @access public - * @param mixed $value 缓存数据 - * @param array $options 缓存参数 - * @param array $bind 绑定参数 - * @return string + * @param array $resultSet + * @return void */ - protected function getCacheKey($value, $options, $bind = []) + protected function resultSet(&$resultSet) { - if (is_scalar($value)) { - $data = $value; - } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { - $data = $value[1]; - } - $prefix = $this->connection->getConfig('database') . '.'; - - if (isset($data)) { - return 'think:' . $prefix . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } } - try { - return md5($prefix . serialize($options) . serialize($bind)); - } catch (\Exception $e) { - throw new Exception('closure not support cache(true)'); + if (!empty($this->options['with_attr'])) { + foreach ($resultSet as &$result) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + if (!empty($this->options['collection']) || 'collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); } } /** * 查找单条记录 * @access public - * @param array|string|Query|\Closure $data - * @return array|false|\PDOStatement|string|Model + * @param array|string|Query|\Closure $data + * @return array|null|\PDOStatement|string|Model * @throws DbException * @throws ModelNotFoundException * @throws DataNotFoundException @@ -2631,102 +3136,292 @@ class Query if ($data instanceof Query) { return $data->find(); } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); + $data($this); $data = null; } - // 分析查询表达式 - $options = $this->parseExpress(); - $pk = $this->getPk($options); + + $this->parseOptions(); + if (!is_null($data)) { // AR模式分析主键条件 - $this->parsePkWhere($data, $options); - } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + $this->parsePkWhere($data); } - $options['limit'] = 1; - $result = false; - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - if (true === $cache['key'] && !is_null($data) && !is_array($data)) { - $key = 'think:' . $this->connection->getConfig('database') . '.' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; - } elseif (is_string($cache['key'])) { - $key = $cache['key']; - } elseif (!isset($key)) { - $key = md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); - } - $result = Cache::get($key); - } - if (false === $result) { - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - if (is_string($pk)) { - if (!is_array($data)) { - if (isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $item[$pk] = $val; - } else { - $item[$pk] = $data; - } - $data = $item; - } - } - $options['data'] = $data; - // 事件回调 - if ($result = $this->trigger('before_find', $options)) { - } else { - // 执行查询 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + $this->options['data'] = $data; - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - $result = isset($resultSet[0]) ? $resultSet[0] : null; - } + $result = $this->connection->find($this); - if (isset($cache) && $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } + if ($this->options['fetch_sql']) { + return $result; } // 数据处理 - if (!empty($result)) { - if (!empty($this->model)) { - // 返回模型对象 - $result = $this->model->newInstance($result); - $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); - // 关联查询 - if (!empty($options['relation'])) { - $result->relationQuery($options['relation']); - } - // 预载入查询 - if (!empty($options['with'])) { - $result->eagerlyResult($result, $options['with']); - } - // 关联统计 - if (!empty($options['with_count'])) { - $result->relationCount($result, $options['with_count']); - } - } - } elseif (!empty($options['fail'])) { - $this->throwNotFound($options); + if (empty($result)) { + return $this->resultToEmpty(); } + + if (!empty($this->model)) { + // 返回模型对象 + $this->resultToModel($result, $this->options); + } else { + $this->result($result); + } + return $result; } /** - * 查询失败 抛出异常 + * 处理空数据 + * @access protected + * @return array|Model|null + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function resultToEmpty() + { + if (!empty($this->options['allow_empty'])) { + return !empty($this->model) ? $this->model->newInstance([], $this->getModelUpdateCondition($this->options)) : []; + } elseif (!empty($this->options['fail'])) { + $this->throwNotFound($this->options); + } + } + + /** + * 查找单条记录 * @access public - * @param array $options 查询参数 + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @param bool $failException 是否抛出异常 + * @return static|null + * @throws exception\DbException + */ + public function get($data, $with = [], $cache = false, $failException = false) + { + if (is_null($data)) { + return; + } + + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + + return $this->parseQuery($data, $with, $cache) + ->failException($failException) + ->find($data); + } + + /** + * 查找单条记录 如果不存在直接抛出异常 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public function getOrFail($data, $with = [], $cache = false) + { + return $this->get($data, $with, $cache, true); + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static[]|false + * @throws exception\DbException + */ + public function all($data = null, $with = [], $cache = false) + { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + + return $this->parseQuery($data, $with, $cache)->select($data); + } + + /** + * 分析查询表达式 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return Query + */ + protected function parseQuery(&$data, $with, $cache) + { + $result = $this->with($with)->cache($cache); + + if ((is_array($data) && key($data) !== 0) || $data instanceof Where) { + $result = $result->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($result); + $data = null; + } elseif ($data instanceof Query) { + $result = $data->with($with)->cache($cache); + $data = null; + } + + return $result; + } + + /** + * 处理数据 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function result(&$result) + { + if (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); + } + + if (!empty($this->options['with_attr'])) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + /** + * 使用获取器处理数据 + * @access protected + * @param array $result 查询数据 + * @param array $withAttr 字段获取器 + * @return void + */ + protected function getResultAttr(&$result, $withAttr = []) + { + foreach ($withAttr as $name => $closure) { + $name = Loader::parseName($name); + + if (strpos($name, '.')) { + // 支持JSON字段 获取器定义 + list($key, $field) = explode('.', $name); + + if (isset($result[$key])) { + $result[$key][$field] = $closure(isset($result[$key][$field]) ? $result[$key][$field] : null, $result[$key]); + } + } else { + $result[$name] = $closure(isset($result[$name]) ? $result[$name] : null, $result); + } + } + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + protected function jsonResult(&$result, $json = [], $assoc = false, $withRelationAttr = []) + { + foreach ($json as $name) { + if (isset($result[$name])) { + $result[$name] = json_decode($result[$name], $assoc); + + if (isset($withRelationAttr[$name])) { + foreach ($withRelationAttr[$name] as $key => $closure) { + $data = get_object_vars($result[$name]); + $result[$name]->$key = $closure(isset($result[$name]->$key) ? $result[$name]->$key : null, $data); + } + } + } + } + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @param array $options 查询参数 + * @param bool $resultSet 是否为数据集查询 + * @param array $withRelationAttr 关联字段获取器 + * @return void + */ + protected function resultToModel(&$result, $options = [], $resultSet = false, $withRelationAttr = []) + { + // 动态获取器 + if (!empty($options['with_attr']) && empty($withRelationAttr)) { + foreach ($options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + list($relation, $field) = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($options['with_attr'][$name]); + } + } + } + + // JSON 数据处理 + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr); + } + + $result = $this->model->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options)); + + // 动态获取器 + if (!empty($options['with_attr'])) { + $result->withAttribute($options['with_attr']); + } + + // 输出属性控制 + if (!empty($options['visible'])) { + $result->visible($options['visible'], true); + } elseif (!empty($options['hidden'])) { + $result->hidden($options['hidden'], true); + } + + if (!empty($options['append'])) { + $result->append($options['append'], true); + } + + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation'], $withRelationAttr); + } + + // 预载入查询 + if (!$resultSet && !empty($options['with'])) { + $result->eagerlyResult($result, $options['with'], $withRelationAttr); + } + + // JOIN预载入查询 + if (!$resultSet && !empty($options['with_join'])) { + $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true); + } + + // 关联统计 + if (!empty($options['with_count'])) { + foreach ($options['with_count'] as $val) { + $result->relationCount($result, $val[0], $val[1], $val[2]); + } + } + } + + /** + * 获取模型的更新条件 + * @access protected + * @param array $options 查询参数 + */ + protected function getModelUpdateCondition(array $options) + { + return isset($options['where']['AND']) ? $options['where']['AND'] : null; + } + + /** + * 查询失败 抛出异常 + * @access protected + * @param array $options 查询参数 * @throws ModelNotFoundException * @throws DataNotFoundException */ @@ -2735,16 +3430,15 @@ class Query if (!empty($this->model)) { $class = get_class($this->model); throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); - } else { - $table = is_array($options['table']) ? key($options['table']) : $options['table']; - throw new DataNotFoundException('table data not Found:' . $table, $table, $options); } + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); } /** * 查找多条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data + * @param array|string|Query|\Closure $data * @return array|\PDOStatement|string|Model * @throws DbException * @throws ModelNotFoundException @@ -2758,7 +3452,7 @@ class Query /** * 查找单条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data + * @param array|string|Query|\Closure $data * @return array|\PDOStatement|string|Model * @throws DbException * @throws ModelNotFoundException @@ -2769,42 +3463,57 @@ class Query return $this->failException(true)->find($data); } + /** + * 查找单条记录 不存在则返回空模型 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrEmpty($data = null) + { + return $this->allowEmpty(true)->find($data); + } + /** * 分批数据返回处理 * @access public - * @param integer $count 每次处理的数据数量 - * @param callable $callback 处理回调方法 - * @param string $column 分批处理的字段名 - * @param string $order 排序规则 + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 * @return boolean - * @throws \LogicException + * @throws DbException */ public function chunk($count, $callback, $column = null, $order = 'asc') { $options = $this->getOptions(); - if (empty($options['table'])) { - $options['table'] = $this->getTable(); - } - $column = $column ?: $this->getPk($options); + $column = $column ?: $this->getPk($options); if (isset($options['order'])) { - if (App::$debug) { - throw new \LogicException('chunk not support call order'); + if (Container::get('app')->isDebug()) { + throw new DbException('chunk not support call order'); } unset($options['order']); } + $bind = $this->bind; + if (is_array($column)) { $times = 1; $query = $this->options($options)->page($times, $count); } else { + $query = $this->options($options)->limit($count); + if (strpos($column, '.')) { list($alias, $key) = explode('.', $column); } else { $key = $column; } - $query = $this->options($options)->limit($count); } + $resultSet = $query->order($column, $order)->select(); while (count($resultSet) > 0) { @@ -2816,13 +3525,14 @@ class Query return false; } - if (is_array($column)) { + if (isset($times)) { $times++; $query = $this->options($options)->page($times, $count); } else { $end = end($resultSet); $lastId = is_array($end) ? $end[$key] : $end->getData($key); - $query = $this->options($options) + + $query = $this->options($options) ->limit($count) ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); } @@ -2836,19 +3546,23 @@ class Query /** * 获取绑定的参数 并清空 * @access public + * @param bool $clear * @return array */ - public function getBind() + public function getBind($clear = true) { - $bind = $this->bind; - $this->bind = []; + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + return $bind; } /** * 创建子查询SQL * @access public - * @param bool $sub + * @param bool $sub * @return string * @throws DbException */ @@ -2858,65 +3572,101 @@ class Query } /** - * 删除记录 - * @access public - * @param mixed $data 表达式 true 表示强制删除 - * @return int - * @throws Exception - * @throws PDOException + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void */ - public function delete($data = null) + protected function parseView(&$options) { - // 分析查询表达式 - $options = $this->parseExpress(); - $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache']['key'])) { - $key = $options['cache']['key']; + if (!isset($options['map'])) { + return; } - if (!is_null($data) && true !== $data) { - if (!isset($key) && !is_array($data)) { - // 缓存标识 - $key = 'think:' . $options['table'] . '|' . $data; + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } } - // AR模式分析主键条件 - $this->parsePkWhere($data, $options); - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); } - if (true !== $data && empty($options['where'])) { - // 如果条件为空 不进行删除操作 除非设置 1=1 - throw new Exception('delete without condition'); - } - // 生成删除SQL语句 - $sql = $this->builder->delete($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - - // 检测缓存 - if (isset($key) && Cache::get($key)) { - // 删除缓存 - Cache::rm($key); - } elseif (!empty($options['cache']['tag'])) { - Cache::clear($options['cache']['tag']); - } - // 执行操作 - $result = $this->execute($sql, $bind, $this); - if ($result) { - if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $item[$pk] = $val; - $data = $item; + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key) && is_string($val)) { + if (strpos($val, ' ')) { + list($field, $sort) = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } } - $options['data'] = $data; - $this->trigger('after_delete', $options); } - return $result; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data) + { + $pk = $this->getPk($this->options); + // 获取当前数据表 + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; + } else { + $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$key] = [$attr, '=', $data[$key]]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($this->options['where']['AND'])) { + $this->options['where']['AND'] = array_merge($this->options['where']['AND'], $where); + } else { + $this->options['where']['AND'] = $where; + } + } + + return; } /** @@ -2924,9 +3674,9 @@ class Query * @access protected * @return array */ - protected function parseExpress() + protected function parseOptions() { - $options = $this->options; + $options = $this->getOptions(); // 获取数据表 if (empty($options['table'])) { @@ -2937,48 +3687,17 @@ class Query $options['where'] = []; } elseif (isset($options['view'])) { // 视图查询条件处理 - foreach (['AND', 'OR'] as $logic) { - if (isset($options['where'][$logic])) { - foreach ($options['where'][$logic] as $key => $val) { - if (array_key_exists($key, $options['map'])) { - $options['where'][$logic][$options['map'][$key]] = $val; - unset($options['where'][$logic][$key]); - } - } - } - } - - if (isset($options['order'])) { - // 视图查询排序处理 - if (is_string($options['order'])) { - $options['order'] = explode(',', $options['order']); - } - foreach ($options['order'] as $key => $val) { - if (is_numeric($key)) { - if (strpos($val, ' ')) { - list($field, $sort) = explode(' ', $val); - if (array_key_exists($field, $options['map'])) { - $options['order'][$options['map'][$field]] = $sort; - unset($options['order'][$key]); - } - } elseif (array_key_exists($val, $options['map'])) { - $options['order'][$options['map'][$val]] = 'asc'; - unset($options['order'][$key]); - } - } elseif (array_key_exists($key, $options['map'])) { - $options['order'][$options['map'][$key]] = $val; - unset($options['order'][$key]); - } - } - } + $this->parseView($options); } if (!isset($options['field'])) { $options['field'] = '*'; } - if (!isset($options['data'])) { - $options['data'] = []; + foreach (['data', 'order', 'join', 'union'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } } if (!isset($options['strict'])) { @@ -2995,7 +3714,7 @@ class Query $options['master'] = true; } - foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { + foreach (['group', 'having', 'limit', 'force', 'comment'] as $name) { if (!isset($options[$name])) { $options[$name] = ''; } @@ -3010,15 +3729,16 @@ class Query $options['limit'] = $offset . ',' . $listRows; } - $this->options = []; + $this->options = $options; + return $options; } /** * 注册回调方法 * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 + * @param string $event 事件名 + * @param callable $callback 回调方法 * @return void */ public static function event($event, $callback) @@ -3028,18 +3748,19 @@ class Query /** * 触发事件 - * @access protected - * @param string $event 事件名 - * @param mixed $params 额外参数 + * @access public + * @param string $event 事件名 * @return bool */ - protected function trigger($event, $params = []) + public function trigger($event) { $result = false; + if (isset(self::$event[$event])) { - $callback = self::$event[$event]; - $result = call_user_func_array($callback, [$params, $this]); + $result = Container::getInstance()->invoke(self::$event[$event], [$this]); } + return $result; } + } diff --git a/Server/thinkphp/library/think/db/builder/Mysql.php b/Server/thinkphp/library/think/db/builder/Mysql.php index be2af714..f7384b31 100644 --- a/Server/thinkphp/library/think/db/builder/Mysql.php +++ b/Server/thinkphp/library/think/db/builder/Mysql.php @@ -12,6 +12,8 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Expression; +use think\db\Query; use think\Exception; /** @@ -19,6 +21,20 @@ use think\Exception; */ class Mysql extends Builder { + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; @@ -26,67 +42,80 @@ class Mysql extends Builder /** * 生成insertall SQL * @access public - * @param array $dataSet 数据集 - * @param array $options 表达式 - * @param bool $replace 是否replace + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace * @return string - * @throws Exception */ - public function insertAll($dataSet, $options = [], $replace = false) + public function insertAll(Query $query, $dataSet, $replace = false) { + $options = $query->getOptions(); + // 获取合法的字段 if ('*' == $options['field']) { - $fields = array_keys($this->query->getFieldsType($options['table'])); + $allowFields = $this->connection->getTableFields($options['table']); } else { - $fields = $options['field']; + $allowFields = $options['field']; } - foreach ($dataSet as $data) { - foreach ($data as $key => $val) { - if (!in_array($key, $fields, true)) { - if ($options['strict']) { - throw new Exception('fields not exists:[' . $key . ']'); - } - unset($data[$key]); - } elseif (is_null($val)) { - $data[$key] = 'NULL'; - } elseif (is_scalar($val)) { - $data[$key] = $this->parseValue($val, $key); - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $data[$key] = $val->__toString(); - } else { - // 过滤掉非标量数据 - unset($data[$key]); - } - } - $value = array_values($data); - $values[] = '( ' . implode(',', $value) . ' )'; + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; if (!isset($insertFields)) { - $insertFields = array_map([$this, 'parseKey'], array_keys($data)); + $insertFields = array_keys($data); } } + $fields = []; + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + return str_replace( ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), - implode(' , ', $insertFields), + $this->parseTable($query, $options['table']), + implode(' , ', $fields), implode(' , ', $values), - $this->parseComment($options['comment']), - ], $this->insertAllSql); + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, $key, $exp, $value, $field) + { + if ($value instanceof Expression) { + $value = $value->getValue(); + } + + return $key . ' ' . $exp . ' ' . $value; } /** * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 * @return string */ - protected function parseKey($key, $options = [], $strict = false) + public function parseKey(Query $query, $key, $strict = false) { if (is_numeric($key)) { return $key; @@ -95,41 +124,59 @@ class Mysql extends Builder } $key = trim($key); - if (strpos($key, '$.') && false === strpos($key, '(')) { + + if(strpos($key, '->>') && false === strpos($key, '(')){ // JSON字段支持 - list($field, $name) = explode('$.', $key); - return 'json_extract(' . $field . ', \'$.' . $name . '\')'; + list($field, $name) = explode('->>', $key, 2); + + return $this->parseKey($query, $field, true) . '->>\'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->>', '.', $name) . '\''; + } + elseif (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('->', $key, 2); + + return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { throw new Exception('not support data:' . $key); } - if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } + if (isset($table)) { if (strpos($table, '.')) { $table = str_replace('.', '`.`', $table); } + $key = '`' . $table . '`.' . $key; } + return $key; } /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'rand()'; } diff --git a/Server/thinkphp/library/think/db/builder/Pgsql.php b/Server/thinkphp/library/think/db/builder/Pgsql.php index acc22896..742c7db3 100644 --- a/Server/thinkphp/library/think/db/builder/Pgsql.php +++ b/Server/thinkphp/library/think/db/builder/Pgsql.php @@ -12,24 +12,28 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Query; /** * Pgsql数据库驱动 */ class Pgsql extends Builder { + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; /** * limit分析 * @access protected - * @param mixed $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - public function parseLimit($limit) + public function parseLimit(Query $query, $limit) { $limitStr = ''; + if (!empty($limit)) { $limit = explode(',', $limit); if (count($limit) > 1) { @@ -38,17 +42,19 @@ class Pgsql extends Builder $limitStr .= ' LIMIT ' . $limit[0] . ' '; } } + return $limitStr; } /** * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 * @return string */ - protected function parseKey($key, $options = [], $strict = false) + public function parseKey(Query $query, $key, $strict = false) { if (is_numeric($key)) { return $key; @@ -57,31 +63,40 @@ class Pgsql extends Builder } $key = trim($key); - if (strpos($key, '$.') && false === strpos($key, '(')) { + + if (strpos($key, '->') && false === strpos($key, '(')) { // JSON字段支持 - list($field, $name) = explode('$.', $key); + list($field, $name) = explode('->', $key); $key = $field . '->>\'' . $name . '\''; } elseif (strpos($key, '.')) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } + if (isset($table)) { $key = $table . '.' . $key; } + return $key; } /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'RANDOM()'; } diff --git a/Server/thinkphp/library/think/db/builder/Sqlite.php b/Server/thinkphp/library/think/db/builder/Sqlite.php index c727f04b..2b887ca8 100644 --- a/Server/thinkphp/library/think/db/builder/Sqlite.php +++ b/Server/thinkphp/library/think/db/builder/Sqlite.php @@ -12,6 +12,7 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Query; /** * Sqlite数据库驱动 @@ -22,12 +23,14 @@ class Sqlite extends Builder /** * limit * @access public - * @param string $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - public function parseLimit($limit) + public function parseLimit(Query $query, $limit) { $limitStr = ''; + if (!empty($limit)) { $limit = explode(',', $limit); if (count($limit) > 1) { @@ -36,27 +39,30 @@ class Sqlite extends Builder $limitStr .= ' LIMIT ' . $limit[0] . ' '; } } + return $limitStr; } /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'RANDOM()'; } /** * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 * @return string */ - protected function parseKey($key, $options = [], $strict = false) + public function parseKey(Query $query, $key, $strict = false) { if (is_numeric($key)) { return $key; @@ -65,18 +71,26 @@ class Sqlite extends Builder } $key = trim($key); + if (strpos($key, '.')) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } + if (isset($table)) { $key = $table . '.' . $key; } + return $key; } } diff --git a/Server/thinkphp/library/think/db/builder/Sqlsrv.php b/Server/thinkphp/library/think/db/builder/Sqlsrv.php index dc425d9e..ef27aafa 100644 --- a/Server/thinkphp/library/think/db/builder/Sqlsrv.php +++ b/Server/thinkphp/library/think/db/builder/Sqlsrv.php @@ -13,6 +13,8 @@ namespace think\db\builder; use think\db\Builder; use think\db\Expression; +use think\db\Query; +use think\Exception; /** * Sqlsrv数据库驱动 @@ -22,116 +24,136 @@ class Sqlsrv extends Builder protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; - protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; /** * order分析 * @access protected - * @param mixed $order - * @param array $options + * @param Query $query 查询对象 + * @param mixed $order * @return string */ - protected function parseOrder($order, $options = []) + protected function parseOrder(Query $query, $order) { if (empty($order)) { return ' ORDER BY rand()'; } - $array = []; foreach ($order as $key => $val) { if ($val instanceof Expression) { $array[] = $val->getValue(); - } elseif (is_numeric($key)) { - if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val, $options); - } elseif ('[rand]' == $val) { - $array[] = $this->parseRand(); - } else { - $array[] = $val; - } + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); } else { - $sort = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : ''; - $array[] = $this->parseKey($key, $options, true) . ' ' . $sort; + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } } } - return ' ORDER BY ' . implode(',', $array); + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); } /** * 随机排序 * @access protected + * @param Query $query 查询对象 * @return string */ - protected function parseRand() + protected function parseRand(Query $query) { return 'rand()'; } /** * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 * @return string */ - protected function parseKey($key, $options = [], $strict = false) + public function parseKey(Query $query, $key, $strict = false) { if (is_numeric($key)) { return $key; } elseif ($key instanceof Expression) { return $key->getValue(); } + $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + if ('__TABLE__' == $table) { - $table = $this->query->getTable(); + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; + + if (isset($alias[$table])) { + $table = $alias[$table]; } } if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { throw new Exception('not support data:' . $key); } - if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { $key = '[' . $key . ']'; } + if (isset($table)) { $key = '[' . $table . '].' . $key; } + return $key; } /** * limit * @access protected - * @param mixed $limit + * @param Query $query 查询对象 + * @param mixed $limit * @return string */ - protected function parseLimit($limit) + protected function parseLimit(Query $query, $limit) { if (empty($limit)) { return ''; } $limit = explode(',', $limit); + if (count($limit) > 1) { $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; } else { $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; } + return 'WHERE ' . $limitStr; } - public function selectInsert($fields, $table, $options) + public function selectInsert(Query $query, $fields, $table) { $this->selectSql = $this->selectInsertSql; - return parent::selectInsert($fields, $table, $options); + + return parent::selectInsert($query, $fields, $table); } } diff --git a/Server/thinkphp/library/think/db/connector/Mysql.php b/Server/thinkphp/library/think/db/connector/Mysql.php index be1a85ce..cfd2ac72 100644 --- a/Server/thinkphp/library/think/db/connector/Mysql.php +++ b/Server/thinkphp/library/think/db/connector/Mysql.php @@ -13,7 +13,7 @@ namespace think\db\connector; use PDO; use think\db\Connection; -use think\Log; +use think\db\Query; /** * mysql数据库驱动 @@ -23,10 +23,32 @@ class Mysql extends Connection protected $builder = '\\think\\db\\builder\\Mysql'; + /** + * 初始化 + * @access protected + * @return void + */ + protected function initialize() + { + // Point类型支持 + Query::extend('point', function ($query, $field, $value = null, $fun = 'GeomFromText', $type = 'POINT') { + if (!is_null($value)) { + $query->data($field, ['point', $value, $fun, $type]); + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + $query->setOption('point', $field); + } + + return $query; + }); + } + /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) @@ -43,48 +65,53 @@ class Mysql extends Connection if (!empty($config['charset'])) { $dsn .= ';charset=' . $config['charset']; } + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) { list($tableName) = explode(' ', $tableName); + if (false === strpos($tableName, '`')) { if (strpos($tableName, '.')) { $tableName = str_replace('.', '`.`', $tableName); } $tableName = '`' . $tableName . '`'; } + $sql = 'SHOW COLUMNS FROM ' . $tableName; $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); $info[$val['field']] = [ 'name' => $val['field'], 'type' => $val['type'], - 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes + 'notnull' => 'NO' == $val['null'], 'default' => $val['default'], - 'primary' => (strtolower($val['key']) == 'pri'), - 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + 'primary' => strtolower($val['key']) == 'pri', + 'autoinc' => strtolower($val['extra']) == 'auto_increment', ]; } } + return $this->fieldCase($info); } /** * 取得数据库的表信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') @@ -93,28 +120,52 @@ class Mysql extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) { - $pdo = $this->linkID->query("EXPLAIN " . $sql); - $result = $pdo->fetch(PDO::FETCH_ASSOC); - $result = array_change_key_case($result); - if (isset($result['extra'])) { - if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { - Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + $pdo = $this->linkID->prepare("EXPLAIN " . $this->queryStr); + + foreach ($this->bind as $key => $val) { + // 占位符 + $param = is_int($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $pdo->bindValue($param, $val[0], $val[1]); + } else { + $result = $pdo->bindValue($param, $val); } } + + $pdo->execute(); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + $result = array_change_key_case($result); + + if (isset($result['extra'])) { + if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { + $this->log('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + } + } + return $result; } @@ -123,4 +174,56 @@ class Mysql extends Connection return true; } + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa($xid) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + $this->linkID->exec("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa($xid) + { + $this->initConnect(true); + $this->linkID->exec("XA END '$xid'"); + $this->linkID->exec("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa($xid) + { + $this->initConnect(true); + $this->linkID->exec("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa($xid) + { + $this->initConnect(true); + $this->linkID->exec("XA ROLLBACK '$xid'"); + } } diff --git a/Server/thinkphp/library/think/db/connector/Pgsql.php b/Server/thinkphp/library/think/db/connector/Pgsql.php index bbcf5768..ee9fca01 100644 --- a/Server/thinkphp/library/think/db/connector/Pgsql.php +++ b/Server/thinkphp/library/think/db/connector/Pgsql.php @@ -21,36 +21,46 @@ class Pgsql extends Connection { protected $builder = '\\think\\db\\builder\\Pgsql'; + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) { $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + if (!empty($config['hostport'])) { $dsn .= ';port=' . $config['hostport']; } + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) { - list($tableName) = explode(' ', $tableName); $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); @@ -64,13 +74,14 @@ class Pgsql extends Connection ]; } } + return $this->fieldCase($info); } /** * 取得数据库的表信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') @@ -79,16 +90,18 @@ class Pgsql extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) diff --git a/Server/thinkphp/library/think/db/connector/Sqlite.php b/Server/thinkphp/library/think/db/connector/Sqlite.php index c4e3a724..5b9b3fa6 100644 --- a/Server/thinkphp/library/think/db/connector/Sqlite.php +++ b/Server/thinkphp/library/think/db/connector/Sqlite.php @@ -25,19 +25,20 @@ class Sqlite extends Connection /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) { $dsn = 'sqlite:' . $config['database']; + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) @@ -48,6 +49,7 @@ class Sqlite extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); @@ -61,18 +63,18 @@ class Sqlite extends Connection ]; } } + return $this->fieldCase($info); } /** * 取得数据库的表信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') { - $sql = "SELECT name FROM sqlite_master WHERE type='table' " . "UNION ALL SELECT name FROM sqlite_temp_master " . "WHERE type='table' ORDER BY name"; @@ -80,16 +82,18 @@ class Sqlite extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) diff --git a/Server/thinkphp/library/think/db/connector/Sqlsrv.php b/Server/thinkphp/library/think/db/connector/Sqlsrv.php index 35c66005..123affb8 100644 --- a/Server/thinkphp/library/think/db/connector/Sqlsrv.php +++ b/Server/thinkphp/library/think/db/connector/Sqlsrv.php @@ -13,6 +13,7 @@ namespace think\db\connector; use PDO; use think\db\Connection; +use think\db\Query; /** * Sqlsrv数据库驱动 @@ -23,28 +24,33 @@ class Sqlsrv extends Connection protected $params = [ PDO::ATTR_CASE => PDO::CASE_NATURAL, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, PDO::ATTR_STRINGIFY_FETCHES => false, ]; + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + /** * 解析pdo连接的dsn信息 * @access protected - * @param array $config 连接信息 + * @param array $config 连接信息 * @return string */ protected function parseDsn($config) { $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + if (!empty($config['hostport'])) { $dsn .= ',' . $config['hostport']; } + return $dsn; } /** * 取得数据表的字段信息 * @access public - * @param string $tableName + * @param string $tableName * @return array */ public function getFields($tableName) @@ -64,6 +70,7 @@ class Sqlsrv extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + if ($result) { foreach ($result as $key => $val) { $val = array_change_key_case($val); @@ -77,23 +84,30 @@ class Sqlsrv extends Connection ]; } } + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + // 调试开始 $this->debug(true); + $pdo = $this->linkID->query($sql); + // 调试结束 $this->debug(false, $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + if ($result) { $info[$result['column_name']]['primary'] = true; } + return $this->fieldCase($info); } /** * 取得数据表的字段信息 * @access public - * @param string $dbName + * @param string $dbName * @return array */ public function getTables($dbName = '') @@ -106,16 +120,112 @@ class Sqlsrv extends Connection $pdo = $this->query($sql, [], false, true); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; + foreach ($result as $key => $val) { $info[$key] = current($val); } + return $info; } + /** + * 得到某个列的数组 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(Query $query, $field, $key = '') + { + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + $guid = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $field); + + $result = Container::get('cache')->get($guid); + + if (false !== $result) { + return $result; + } + } + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if ('*' == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + + if (3 == $count) { + $column = $key2; + } elseif ($count < 3) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; + } + } + + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + + return $result; + } + /** * SQL性能分析 * @access protected - * @param string $sql + * @param string $sql * @return array */ protected function getExplain($sql) diff --git a/Server/thinkphp/library/think/db/connector/pgsql.sql b/Server/thinkphp/library/think/db/connector/pgsql.sql index e1a09a30..5a4442d0 100644 --- a/Server/thinkphp/library/think/db/connector/pgsql.sql +++ b/Server/thinkphp/library/think/db/connector/pgsql.sql @@ -37,6 +37,10 @@ DECLARE v_sql varchar; v_rec RECORD; v_key varchar; + v_conkey smallint[]; + v_pk varchar[]; + v_len smallint; + v_pos smallint := 1; BEGIN SELECT pg_class.oid INTO v_oid @@ -49,6 +53,31 @@ BEGIN RETURN; END IF; + SELECT + pg_constraint.conkey INTO v_conkey + FROM + pg_constraint + INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid + WHERE + pg_class.relname = a_table_name + AND pg_constraint.contype = 'p'; + + v_len := array_length(v_conkey,1) + 1; + WHILE v_pos < v_len LOOP + SELECT + pg_attribute.attname INTO v_key + FROM pg_constraint + INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = pg_constraint.conkey [ v_conkey[v_pos] ] + INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid + WHERE pg_class.relname = a_table_name AND pg_constraint.contype = 'p'; + v_pk := array_append(v_pk,v_key); + + v_pos := v_pos + 1; + END LOOP; + v_sql=' SELECT pg_attribute.attname AS fields_name, @@ -83,12 +112,19 @@ BEGIN v_ret.fields_not_null=v_rec.fields_not_null; v_ret.fields_default=v_rec.fields_default; v_ret.fields_comment=v_rec.fields_comment; - SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; - IF FOUND THEN - v_ret.fields_key_name=v_key; - ELSE - v_ret.fields_key_name=''; - END IF; + + v_ret.fields_key_name=''; + + v_len := array_length(v_pk,1) + 1; + v_pos := 1; + WHILE v_pos < v_len LOOP + IF v_rec.fields_name = v_pk[v_pos] THEN + v_ret.fields_key_name=v_pk[v_pos]; + EXIT; + END IF; + v_pos := v_pos + 1; + END LOOP; + RETURN NEXT v_ret; END LOOP; RETURN ; diff --git a/Server/thinkphp/library/think/db/exception/BindParamException.php b/Server/thinkphp/library/think/db/exception/BindParamException.php index 4ed19546..dce0c7bf 100644 --- a/Server/thinkphp/library/think/db/exception/BindParamException.php +++ b/Server/thinkphp/library/think/db/exception/BindParamException.php @@ -21,11 +21,12 @@ class BindParamException extends DbException /** * BindParamException constructor. - * @param string $message - * @param array $config - * @param string $sql - * @param array $bind - * @param int $code + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code */ public function __construct($message, $config, $sql, $bind, $code = 10502) { diff --git a/Server/thinkphp/library/think/db/exception/DataNotFoundException.php b/Server/thinkphp/library/think/db/exception/DataNotFoundException.php index f2542ac6..883e333e 100644 --- a/Server/thinkphp/library/think/db/exception/DataNotFoundException.php +++ b/Server/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -19,9 +19,10 @@ class DataNotFoundException extends DbException /** * DbException constructor. - * @param string $message - * @param string $table - * @param array $config + * @access public + * @param string $message + * @param string $table + * @param array $config */ public function __construct($message, $table = '', array $config = []) { diff --git a/Server/thinkphp/library/think/db/exception/ModelNotFoundException.php b/Server/thinkphp/library/think/db/exception/ModelNotFoundException.php index 6e5f930c..ae52baf3 100644 --- a/Server/thinkphp/library/think/db/exception/ModelNotFoundException.php +++ b/Server/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -19,8 +19,10 @@ class ModelNotFoundException extends DbException /** * 构造方法 - * @param string $message - * @param string $model + * @access public + * @param string $message + * @param string $model + * @param array $config */ public function __construct($message, $model = '', array $config = []) { diff --git a/Server/thinkphp/library/think/debug/Console.php b/Server/thinkphp/library/think/debug/Console.php index c17911b2..5cbaa0f2 100644 --- a/Server/thinkphp/library/think/debug/Console.php +++ b/Server/thinkphp/library/think/debug/Console.php @@ -11,11 +11,8 @@ namespace think\debug; -use think\Cache; -use think\Config; +use think\Container; use think\Db; -use think\Debug; -use think\Request; use think\Response; /** @@ -24,7 +21,7 @@ use think\Response; class Console { protected $config = [ - 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], ]; // 实例化并传入参数 @@ -38,13 +35,13 @@ class Console /** * 调试输出接口 * @access public - * @param Response $response Response对象 - * @param array $log 日志信息 + * @param Response $response Response对象 + * @param array $log 日志信息 * @return bool */ public function output(Response $response, array $log = []) { - $request = Request::instance(); + $request = Container::get('request'); $contentType = $response->getHeader('Content-Type'); $accept = $request->header('accept'); if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { @@ -53,12 +50,12 @@ class Console return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - THINK_START_TIME, 10); + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); - if (isset($_SERVER['HTTP_HOST'])) { - $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); } else { $uri = 'cmd:' . implode(' ', $_SERVER['argv']); } @@ -68,19 +65,18 @@ class Console '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', - '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', - '配置加载' => count(Config::get()), + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', ]; if (session_id()) { $base['会话信息'] = 'SESSION_ID=' . session_id(); } - $info = Debug::getFile(true); + $info = Container::get('debug')->getFile(true); // 页面Trace信息 $trace = []; - foreach ($this->config['trace_tabs'] as $name => $title) { + foreach ($this->config['tabs'] as $name => $title) { $name = strtolower($name); switch ($name) { case 'base': // 基本信息 @@ -121,7 +117,7 @@ JS; protected function console($type, $msg) { $type = strtolower($type); - $trace_tabs = array_values($this->config['trace_tabs']); + $trace_tabs = array_values($this->config['tabs']); $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) ? "console.group('{$type}');" : "console.groupCollapsed('{$type}');"; @@ -137,12 +133,12 @@ JS; } break; case '错误': - $msg = str_replace("\n", '\n', json_encode($m)); + $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m))); $style = 'color:#F4006B;font-size:14px;'; $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; break; case 'sql': - $msg = str_replace("\n", '\n', $m); + $msg = str_replace("\n", '\n', addslashes($m)); $style = "color:#009bb4;"; $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; break; diff --git a/Server/thinkphp/library/think/debug/Html.php b/Server/thinkphp/library/think/debug/Html.php index b6be7adb..a123762e 100644 --- a/Server/thinkphp/library/think/debug/Html.php +++ b/Server/thinkphp/library/think/debug/Html.php @@ -11,11 +11,8 @@ namespace think\debug; -use think\Cache; -use think\Config; +use think\Container; use think\Db; -use think\Debug; -use think\Request; use think\Response; /** @@ -24,27 +21,26 @@ use think\Response; class Html { protected $config = [ - 'trace_file' => '', - 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + 'file' => '', + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], ]; // 实例化并传入参数 public function __construct(array $config = []) { - $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; - $this->config = array_merge($this->config, $config); + $this->config = array_merge($this->config, $config); } /** * 调试输出接口 * @access public - * @param Response $response Response对象 - * @param array $log 日志信息 + * @param Response $response Response对象 + * @param array $log 日志信息 * @return bool */ public function output(Response $response, array $log = []) { - $request = Request::instance(); + $request = Container::get('request'); $contentType = $response->getHeader('Content-Type'); $accept = $request->header('accept'); if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { @@ -53,13 +49,13 @@ class Html return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - THINK_START_TIME, 10, '.', ''); + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10, '.', ''); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); // 页面Trace信息 - if (isset($_SERVER['HTTP_HOST'])) { - $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); } else { $uri = 'cmd:' . implode(' ', $_SERVER['argv']); } @@ -67,19 +63,18 @@ class Html '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', - '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', - '配置加载' => count(Config::get()), + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', ]; if (session_id()) { $base['会话信息'] = 'SESSION_ID=' . session_id(); } - $info = Debug::getFile(true); + $info = Container::get('debug')->getFile(true); // 页面Trace信息 $trace = []; - foreach ($this->config['trace_tabs'] as $name => $title) { + foreach ($this->config['tabs'] as $name => $title) { $name = strtolower($name); switch ($name) { case 'base': // 基本信息 @@ -104,7 +99,7 @@ class Html } // 调用Trace页面模板 ob_start(); - include $this->config['trace_file']; + include $this->config['file']; return ob_get_clean(); } diff --git a/Server/thinkphp/library/think/exception/DbException.php b/Server/thinkphp/library/think/exception/DbException.php index 0ae80ad1..6baafb51 100644 --- a/Server/thinkphp/library/think/exception/DbException.php +++ b/Server/thinkphp/library/think/exception/DbException.php @@ -20,12 +20,13 @@ class DbException extends Exception { /** * DbException constructor. - * @param string $message - * @param array $config - * @param string $sql - * @param int $code + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code */ - public function __construct($message, array $config, $sql, $code = 10500) + public function __construct($message, array $config = [], $sql = '', $code = 10500) { $this->message = $message; $this->code = $code; diff --git a/Server/thinkphp/library/think/exception/ErrorException.php b/Server/thinkphp/library/think/exception/ErrorException.php index b3a9a30a..3143b8f7 100644 --- a/Server/thinkphp/library/think/exception/ErrorException.php +++ b/Server/thinkphp/library/think/exception/ErrorException.php @@ -29,25 +29,24 @@ class ErrorException extends Exception /** * 错误异常构造函数 - * @param integer $severity 错误级别 - * @param string $message 错误详细信息 - * @param string $file 出错文件路径 - * @param integer $line 出错行号 - * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 + * @access public + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 */ - public function __construct($severity, $message, $file, $line, array $context = []) + public function __construct($severity, $message, $file, $line) { $this->severity = $severity; $this->message = $message; $this->file = $file; $this->line = $line; $this->code = 0; - - empty($context) || $this->setData('Error Context', $context); } /** * 获取错误级别 + * @access public * @return integer 错误级别 */ final public function getSeverity() diff --git a/Server/thinkphp/library/think/exception/Handle.php b/Server/thinkphp/library/think/exception/Handle.php index f523db09..02c85ec1 100644 --- a/Server/thinkphp/library/think/exception/Handle.php +++ b/Server/thinkphp/library/think/exception/Handle.php @@ -12,11 +12,8 @@ namespace think\exception; use Exception; -use think\App; -use think\Config; use think\console\Output; -use think\Lang; -use think\Log; +use think\Container; use think\Response; class Handle @@ -34,6 +31,7 @@ class Handle /** * Report or log an exception. * + * @access public * @param \Exception $exception * @return void */ @@ -41,7 +39,7 @@ class Handle { if (!$this->isIgnoreReport($exception)) { // 收集异常数据 - if (App::$debug) { + if (Container::get('app')->isDebug()) { $data = [ 'file' => $exception->getFile(), 'line' => $exception->getLine(), @@ -57,11 +55,11 @@ class Handle $log = "[{$data['code']}]{$data['message']}"; } - if (Config::get('record_trace')) { + if (Container::get('app')->config('log.record_trace')) { $log .= "\r\n" . $exception->getTraceAsString(); } - Log::record($log, 'error'); + Container::get('log')->record($log, 'error'); } } @@ -72,12 +70,14 @@ class Handle return true; } } + return false; } /** * Render an exception into an HTTP response. * + * @access public * @param \Exception $e * @return Response */ @@ -85,6 +85,7 @@ class Handle { if ($this->render && $this->render instanceof \Closure) { $result = call_user_func_array($this->render, [$e]); + if ($result) { return $result; } @@ -98,26 +99,30 @@ class Handle } /** - * @param Output $output - * @param Exception $e + * @access public + * @param Output $output + * @param Exception $e */ public function renderForConsole(Output $output, Exception $e) { - if (App::$debug) { + if (Container::get('app')->isDebug()) { $output->setVerbosity(Output::VERBOSITY_DEBUG); } + $output->renderException($e); } /** - * @param HttpException $e + * @access protected + * @param HttpException $e * @return Response */ protected function renderHttpException(HttpException $e) { $status = $e->getStatusCode(); - $template = Config::get('http_exception_template'); - if (!App::$debug && !empty($template[$status])) { + $template = Container::get('app')->config('http_exception_template'); + + if (!Container::get('app')->isDebug() && !empty($template[$status])) { return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); } else { return $this->convertExceptionToResponse($e); @@ -125,13 +130,14 @@ class Handle } /** - * @param Exception $exception + * @access protected + * @param Exception $exception * @return Response */ protected function convertExceptionToResponse(Exception $exception) { // 收集异常数据 - if (App::$debug) { + if (Container::get('app')->isDebug()) { // 调试模式,获取详细的错误信息 $data = [ 'name' => get_class($exception), @@ -160,9 +166,9 @@ class Handle 'message' => $this->getMessage($exception), ]; - if (!Config::get('show_error_msg')) { + if (!Container::get('app')->config('show_error_msg')) { // 不显示详细错误信息 - $data['message'] = Config::get('error_message'); + $data['message'] = Container::get('app')->config('error_message'); } } @@ -175,10 +181,11 @@ class Handle ob_start(); extract($data); - include Config::get('exception_tmpl'); + include Container::get('app')->config('exception_tmpl'); + // 获取并清空缓存 $content = ob_get_clean(); - $response = new Response($content, 'html'); + $response = Response::create($content, 'html'); if ($exception instanceof HttpException) { $statusCode = $exception->getStatusCode(); @@ -189,52 +196,62 @@ class Handle $statusCode = 500; } $response->code($statusCode); + return $response; } /** * 获取错误编码 * ErrorException则使用错误级别作为错误编码 + * @access protected * @param \Exception $exception * @return integer 错误编码 */ protected function getCode(Exception $exception) { $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { $code = $exception->getSeverity(); } + return $code; } /** * 获取错误信息 * ErrorException则使用错误级别作为错误编码 + * @access protected * @param \Exception $exception * @return string 错误信息 */ protected function getMessage(Exception $exception) { $message = $exception->getMessage(); - if (IS_CLI) { + + if (PHP_SAPI == 'cli') { return $message; } + $lang = Container::get('lang'); + if (strpos($message, ':')) { $name = strstr($message, ':', true); - $message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message; + $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message; } elseif (strpos($message, ',')) { $name = strstr($message, ',', true); - $message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message; - } elseif (Lang::has($message)) { - $message = Lang::get($message); + $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif ($lang->has($message)) { + $message = $lang->get($message); } + return $message; } /** * 获取出错文件内容 * 获取错误的前9行和后9行 + * @access protected * @param \Exception $exception * @return array 错误文件内容 */ @@ -253,30 +270,37 @@ class Handle } catch (Exception $e) { $source = []; } + return $source; } /** * 获取异常扩展信息 * 用于非调试模式html返回类型显示 + * @access protected * @param \Exception $exception * @return array 异常类定义的扩展数据 */ protected function getExtendData(Exception $exception) { $data = []; + if ($exception instanceof \think\Exception) { $data = $exception->getData(); } + return $data; } /** * 获取常量列表 + * @access private * @return array 常量列表 */ private static function getConst() { - return get_defined_constants(true)['user']; + $const = get_defined_constants(true); + + return isset($const['user']) ? $const['user'] : []; } } diff --git a/Server/thinkphp/library/think/exception/PDOException.php b/Server/thinkphp/library/think/exception/PDOException.php index 044f82a0..25240b68 100644 --- a/Server/thinkphp/library/think/exception/PDOException.php +++ b/Server/thinkphp/library/think/exception/PDOException.php @@ -19,10 +19,11 @@ class PDOException extends DbException { /** * PDOException constructor. - * @param \PDOException $exception - * @param array $config - * @param string $sql - * @param int $code + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code */ public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) { diff --git a/Server/thinkphp/library/think/exception/ValidateException.php b/Server/thinkphp/library/think/exception/ValidateException.php index b3684169..81ddfe21 100644 --- a/Server/thinkphp/library/think/exception/ValidateException.php +++ b/Server/thinkphp/library/think/exception/ValidateException.php @@ -15,10 +15,11 @@ class ValidateException extends \RuntimeException { protected $error; - public function __construct($error) + public function __construct($error, $code = 0) { $this->error = $error; - $this->message = is_array($error) ? implode("\n\r", $error) : $error; + $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error; + $this->code = $code; } /** diff --git a/Server/thinkphp/library/think/log/driver/File.php b/Server/thinkphp/library/think/log/driver/File.php index bace4c2f..3f6522d1 100644 --- a/Server/thinkphp/library/think/log/driver/File.php +++ b/Server/thinkphp/library/think/log/driver/File.php @@ -12,7 +12,6 @@ namespace think\log\driver; use think\App; -use think\Request; /** * 本地化调试输出到文件 @@ -20,27 +19,37 @@ use think\Request; class File { protected $config = [ - 'time_format' => ' c ', + 'time_format' => 'c', 'single' => false, 'file_size' => 2097152, - 'path' => LOG_PATH, + 'path' => '', 'apart_level' => [], 'max_files' => 0, 'json' => false, ]; + protected $app; + // 实例化并传入参数 - public function __construct($config = []) + public function __construct(App $app, $config = []) { + $this->app = $app; + if (is_array($config)) { $this->config = array_merge($this->config, $config); } + + if (empty($this->config['path'])) { + $this->config['path'] = $this->app->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR; + } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } } /** * 日志写入接口 * @access public - * @param array $log 日志信息 + * @param array $log 日志信息 * @param bool $append 是否追加请求信息 * @return bool */ @@ -52,6 +61,7 @@ class File !is_dir($path) && mkdir($path, 0755, true); $info = []; + foreach ($log as $type => $val) { foreach ($val as $msg) { @@ -67,6 +77,7 @@ class File $filename = $this->getApartLevelFile($path, $type); $this->write($info[$type], $filename, true, $append); + unset($info[$type]); } } @@ -78,6 +89,45 @@ class File return true; } + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $msg = is_array($msg) ? implode(PHP_EOL, $msg) : $msg; + if (PHP_SAPI == 'cli') { + $info['msg'] = $msg; + $info['type'] = $type; + } else { + $info[$type] = $msg; + } + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + /** * 获取主日志文件名 * @access public @@ -85,21 +135,26 @@ class File */ protected function getMasterLogFile() { + if ($this->config['max_files']) { + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + if ($this->config['single']) { - $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $destination = $this->config['path'] . $name . $cli . '.log'; } else { if ($this->config['max_files']) { $filename = date('Ymd') . $cli . '.log'; - $files = glob($this->config['path'] . '*.log'); - - try { - if (count($files) > $this->config['max_files']) { - unlink($files[0]); - } - } catch (\Exception $e) { - } } else { $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; } @@ -132,45 +187,6 @@ class File return $path . DIRECTORY_SEPARATOR . $name . '_' . $type . $cli . '.log'; } - /** - * 日志写入 - * @access protected - * @param array $message 日志信息 - * @param string $destination 日志文件 - * @param bool $apart 是否独立文件写入 - * @param bool $append 是否追加请求信息 - * @return bool - */ - protected function write($message, $destination, $apart = false, $append = false) - { - // 检测日志文件大小,超过配置大小则备份日志文件重新生成 - $this->checkLogSize($destination); - - // 日志信息封装 - $info['timestamp'] = date($this->config['time_format']); - - foreach ($message as $type => $msg) { - $msg = is_array($msg) ? implode("\r\n", $msg) : $msg; - if (PHP_SAPI == 'cli') { - $info['msg'] = $msg; - $info['type'] = $type; - } else { - $info[$type] = $msg; - } - } - - if (PHP_SAPI == 'cli') { - $message = $this->parseCliLog($info); - } else { - // 添加调试日志 - $this->getDebugLog($info, $append, $apart); - - $message = $this->parseLog($info); - } - - return error_log($message, 3, $destination); - } - /** * 检查日志文件大小并自动生成备份文件 * @access protected @@ -196,14 +212,14 @@ class File protected function parseCliLog($info) { if ($this->config['json']) { - $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; } else { $now = $info['timestamp']; unset($info['timestamp']); - $message = implode("\r\n", $info); + $message = implode(PHP_EOL, $info); - $message = "[{$now}]" . $message . "\r\n"; + $message = "[{$now}]" . $message . PHP_EOL; } return $message; @@ -217,35 +233,34 @@ class File */ protected function parseLog($info) { - $request = Request::instance(); $requestInfo = [ - 'ip' => $request->ip(), - 'method' => $request->method(), - 'host' => $request->host(), - 'uri' => $request->url(), + 'ip' => $this->app['request']->ip(), + 'method' => $this->app['request']->method(), + 'host' => $this->app['request']->host(), + 'uri' => $this->app['request']->url(), ]; if ($this->config['json']) { $info = $requestInfo + $info; - return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; } - array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + array_unshift($info, "---------------------------------------------------------------" . PHP_EOL . "\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); unset($info['timestamp']); - return implode("\r\n", $info) . "\r\n"; + return implode(PHP_EOL, $info) . PHP_EOL; } protected function getDebugLog(&$info, $append, $apart) { - if (App::$debug && $append) { + if ($this->app->isDebug() && $append) { if ($this->config['json']) { // 获取基本信息 - $runtime = round(microtime(true) - THINK_START_TIME, 10); + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); $info = [ 'runtime' => number_format($runtime, 6) . 's', @@ -256,10 +271,10 @@ class File } elseif (!$apart) { // 增加额外的调试信息 - $runtime = round(microtime(true) - THINK_START_TIME, 10); + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; diff --git a/Server/thinkphp/library/think/log/driver/Socket.php b/Server/thinkphp/library/think/log/driver/Socket.php index 4f62915b..5e4f8bfd 100644 --- a/Server/thinkphp/library/think/log/driver/Socket.php +++ b/Server/thinkphp/library/think/log/driver/Socket.php @@ -30,6 +30,8 @@ class Socket 'force_client_ids' => [], // 限制允许读取日志的client_id 'allow_client_ids' => [], + //输出到浏览器默认展开的日志级别 + 'expand_level' => ['debug'], ]; protected $css = [ @@ -41,14 +43,17 @@ class Socket ]; protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + protected $app; /** - * 构造函数 - * @param array $config 缓存参数 + * 架构函数 * @access public + * @param array $config 缓存参数 */ - public function __construct(array $config = []) + public function __construct(App $app, array $config = []) { + $this->app = $app; + if (!empty($config)) { $this->config = array_merge($this->config, $config); } @@ -57,7 +62,7 @@ class Socket /** * 调试输出接口 * @access public - * @param array $log 日志信息 + * @param array $log 日志信息 * @return bool */ public function save(array $log = [], $append = false) @@ -65,12 +70,14 @@ class Socket if (!$this->check()) { return false; } + $trace = []; - if (App::$debug) { - $runtime = round(microtime(true) - THINK_START_TIME, 10); + + if ($this->app->isDebug()) { + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; $file_load = ' [文件加载:' . count(get_included_files()) . ']'; @@ -79,6 +86,7 @@ class Socket } else { $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); } + // 基本信息 $trace[] = [ 'type' => 'group', @@ -89,10 +97,11 @@ class Socket foreach ($log as $type => $val) { $trace[] = [ - 'type' => 'groupCollapsed', + 'type' => in_array($type, $this->config['expand_level']) ? 'group' : 'groupCollapsed', 'msg' => '[ ' . $type . ' ]', 'css' => isset($this->css[$type]) ? $this->css[$type] : '', ]; + foreach ($val as $msg) { if (!is_string($msg)) { $msg = var_export($msg, true); @@ -103,6 +112,7 @@ class Socket 'css' => '', ]; } + $trace[] = [ 'type' => 'groupEnd', 'msg' => '', @@ -116,11 +126,13 @@ class Socket 'msg' => '[ file ]', 'css' => '', ]; + $trace[] = [ 'type' => 'log', 'msg' => implode("\n", get_included_files()), 'css' => '', ]; + $trace[] = [ 'type' => 'groupEnd', 'msg' => '', @@ -135,6 +147,7 @@ class Socket ]; $tabid = $this->getClientArg('tabid'); + if (!$client_id = $this->getClientArg('client_id')) { $client_id = ''; } @@ -148,16 +161,18 @@ class Socket } else { $this->sendToClient($tabid, $client_id, $trace, ''); } + return true; } /** * 发送给指定客户端 + * @access protected * @author Zjmainstay - * @param $tabid - * @param $client_id - * @param $logs - * @param $force_client_id + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id */ protected function sendToClient($tabid, $client_id, $logs, $force_client_id) { @@ -167,20 +182,25 @@ class Socket 'logs' => $logs, 'force_client_id' => $force_client_id, ]; + $msg = @json_encode($logs); $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + $this->send($this->config['host'], $msg, $address); } protected function check() { $tabid = $this->getClientArg('tabid'); + //是否记录日志的检查 if (!$tabid && !$this->config['force_client_ids']) { return false; } + //用户认证 $allow_client_ids = $this->config['allow_client_ids']; + if (!empty($allow_client_ids)) { //通过数组交集得出授权强制推送的client_id $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); @@ -195,6 +215,7 @@ class Socket } else { $this->allowForceClientIds = $this->config['force_client_ids']; } + return true; } @@ -211,6 +232,7 @@ class Socket if (!isset($_SERVER[$key])) { return; } + if (empty($args)) { if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { $args = ['tabid' => null]; @@ -218,32 +240,39 @@ class Socket } parse_str($match[1], $args); } + if (isset($args[$name])) { return $args[$name]; } + return; } /** - * @param string $host - $host of socket server - * @param string $message - 发送的消息 - * @param string $address - 地址 + * @access protected + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 * @return bool */ protected function send($host, $message = '', $address = '/') { $url = 'http://' . $host . ':' . $this->port . $address; $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $message); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $headers = [ "Content-Type: application/json;charset=UTF-8", ]; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + return curl_exec($ch); } diff --git a/Server/thinkphp/library/think/model/Collection.php b/Server/thinkphp/library/think/model/Collection.php index 0406533c..fc0967cf 100644 --- a/Server/thinkphp/library/think/model/Collection.php +++ b/Server/thinkphp/library/think/model/Collection.php @@ -19,21 +19,40 @@ class Collection extends BaseCollection /** * 延迟预载入关联查询 * @access public - * @param mixed $relation 关联 + * @param mixed $relation 关联 * @return $this */ public function load($relation) { - $item = current($this->items); - $item->eagerlyResultSet($this->items, $relation); + if (!$this->isEmpty()) { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + } + + return $this; + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + */ + public function bindAttr($relation, array $attrs = []) + { + $this->each(function (Model $model) use ($relation, $attrs) { + $model->bindAttr($relation, $attrs); + }); + return $this; } /** * 设置需要隐藏的输出属性 * @access public - * @param array $hidden 属性列表 - * @param bool $override 是否覆盖 + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 * @return $this */ public function hidden($hidden = [], $override = false) @@ -42,13 +61,15 @@ class Collection extends BaseCollection /** @var Model $model */ $model->hidden($hidden, $override); }); + return $this; } /** * 设置需要输出的属性 - * @param array $visible - * @param bool $override 是否覆盖 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 * @return $this */ public function visible($visible = [], $override = false) @@ -57,14 +78,15 @@ class Collection extends BaseCollection /** @var Model $model */ $model->visible($visible, $override); }); + return $this; } /** * 设置需要追加的输出属性 * @access public - * @param array $append 属性列表 - * @param bool $override 是否覆盖 + * @param array $append 属性列表 + * @param bool $override 是否覆盖 * @return $this */ public function append($append = [], $override = false) @@ -73,7 +95,24 @@ class Collection extends BaseCollection /** @var Model $model */ $model && $model->append($append, $override); }); + return $this; } + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + $this->each(function ($model) use ($name, $callback) { + /** @var Model $model */ + $model && $model->withAttribute($name, $callback); + }); + + return $this; + } } diff --git a/Server/thinkphp/library/think/model/Pivot.php b/Server/thinkphp/library/think/model/Pivot.php index 13525cdd..a3a395e3 100644 --- a/Server/thinkphp/library/think/model/Pivot.php +++ b/Server/thinkphp/library/think/model/Pivot.php @@ -24,9 +24,9 @@ class Pivot extends Model /** * 架构函数 * @access public - * @param array|object $data 数据 - * @param Model $parent 上级模型 - * @param string $table 中间数据表名 + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 */ public function __construct($data = [], Model $parent = null, $table = '') { diff --git a/Server/thinkphp/library/think/model/Relation.php b/Server/thinkphp/library/think/model/Relation.php index 25fe88db..ac6dd4cf 100644 --- a/Server/thinkphp/library/think/model/Relation.php +++ b/Server/thinkphp/library/think/model/Relation.php @@ -49,7 +49,7 @@ abstract class Relation } /** - * 获取当前的关联模型对象实例 + * 获取当前的关联模型类的实例 * @access public * @return Model */ @@ -59,7 +59,7 @@ abstract class Relation } /** - * 获取关联的查询对象 + * 获取当前的关联模型类的实例 * @access public * @return Query */ @@ -93,7 +93,7 @@ abstract class Relation /** * 封装关联数据集 * @access public - * @param array $resultSet 数据集 + * @param array $resultSet 数据集 * @return mixed */ protected function resultSetBuild($resultSet) @@ -127,6 +127,42 @@ abstract class Relation return $fields; } + protected function getQueryWhere(&$where, $relation) + { + foreach ($where as $key => &$val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } elseif (isset($val[0]) && false === strpos($val[0], '.')) { + $val[0] = $relation . '.' . $val[0]; + } + } + } + + /** + * 更新数据 + * @access public + * @param array $data 更新数据 + * @return integer|string + */ + public function update(array $data = []) + { + return $this->query->update($data); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + return $this->query->delete($data); + } + /** * 执行基础查询(仅执行一次) * @access protected @@ -141,13 +177,9 @@ abstract class Relation // 执行基础查询 $this->baseQuery(); - $result = call_user_func_array([$this->query, $method], $args); - if ($result instanceof Query) { - return $this; - } else { - $this->baseQuery = false; - return $result; - } + $result = call_user_func_array([$this->query->getModel(), $method], $args); + + return $result === $this->query && !in_array(strtolower($method), ['fetchsql', 'fetchpdo']) ? $this : $result; } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } diff --git a/Server/thinkphp/library/think/model/relation/BelongsTo.php b/Server/thinkphp/library/think/model/relation/BelongsTo.php index c1cbab97..056c7d76 100644 --- a/Server/thinkphp/library/think/model/relation/BelongsTo.php +++ b/Server/thinkphp/library/think/model/relation/BelongsTo.php @@ -11,46 +11,51 @@ namespace think\model\relation; -use think\db\Query; +use Closure; use think\Loader; use think\Model; class BelongsTo extends OneToOne { /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param string $joinType JOIN类型 - * @param string $relation 关联名 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) + public function __construct(Model $parent, $model, $foreignKey, $localKey, $relation = null) { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->joinType = $joinType; + $this->joinType = 'INNER'; $this->query = (new $model)->db(); $this->relation = $relation; + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 * @access public - * @return array|false|\PDOStatement|string|Model + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { - $foreignKey = $this->foreignKey; - if ($closure) { - call_user_func_array($closure, [ & $this->query]); + if ($closure instanceof Closure) { + $closure($this->query); } + + $foreignKey = $this->foreignKey; + $relationModel = $this->query ->removeWhereField($this->localKey) ->where($this->localKey, $this->parent->$foreignKey) @@ -65,23 +70,96 @@ class BelongsTo extends OneToOne } /** - * 根据关联条件查询当前模型 + * 创建关联统计子查询 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @return Query + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string */ - public function has($operator = '>=', $count = 1, $id = '*') + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') { - return $this->parent; + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $foreignKey = $this->foreignKey; + + if (!isset($result->$foreignKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->localKey, '=', $result->$foreignKey) + ->$aggregate($field); } /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation]) + ->field($relation . '.' . $localKey) + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -91,28 +169,29 @@ class BelongsTo extends OneToOne $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } + $this->getQueryWhere($where, $relation); } - $fields = $this->getRelationQueryFields($fields, $model); - return $this->parent->db()->alias($model) + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) ->field($fields) ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->where($where); } /** * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) @@ -130,14 +209,14 @@ class BelongsTo extends OneToOne if (!empty($range)) { $this->query->removeWhereField($localKey); - $data = $this->eagerlyWhere($this->query, [ - $localKey => [ - 'in', - $range, - ], + + $data = $this->eagerlyWhere([ + [$localKey, 'in', $range], ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { // 关联模型 @@ -151,7 +230,7 @@ class BelongsTo extends OneToOne if (!empty($this->bindAttr)) { // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); + $this->bindAttr($relationModel, $result); } else { // 设置关联属性 $result->setRelation($attr, $relationModel); @@ -162,19 +241,24 @@ class BelongsTo extends OneToOne /** * 预载入关联查询(数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; + $this->query->removeWhereField($localKey); - $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $relation, $subRelation, $closure); + // 关联模型 if (!isset($data[$result->$foreignKey])) { $relationModel = null; @@ -183,9 +267,10 @@ class BelongsTo extends OneToOne $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + if (!empty($this->bindAttr)) { // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); + $this->bindAttr($relationModel, $result); } else { // 设置关联属性 $result->setRelation(Loader::parseName($relation), $relationModel); @@ -195,15 +280,12 @@ class BelongsTo extends OneToOne /** * 添加关联数据 * @access public - * @param Model $model 关联模型对象 + * @param Model $model 关联模型对象 * @return Model */ public function associate($model) { - $foreignKey = $this->foreignKey; - $pk = $model->getPk(); - - $this->parent->setAttr($foreignKey, $model->$pk); + $this->parent->setAttr($this->foreignKey, $model->getKey()); $this->parent->save(); return $this->parent->setRelation($this->relation, $model); @@ -216,9 +298,7 @@ class BelongsTo extends OneToOne */ public function dissociate() { - $foreignKey = $this->foreignKey; - - $this->parent->setAttr($foreignKey, null); + $this->parent->setAttr($this->foreignKey, null); $this->parent->save(); return $this->parent->setRelation($this->relation, null); diff --git a/Server/thinkphp/library/think/model/relation/BelongsToMany.php b/Server/thinkphp/library/think/model/relation/BelongsToMany.php index a41c45ce..6105e233 100644 --- a/Server/thinkphp/library/think/model/relation/BelongsToMany.php +++ b/Server/thinkphp/library/think/model/relation/BelongsToMany.php @@ -11,8 +11,8 @@ namespace think\model\relation; +use Closure; use think\Collection; -use think\Db; use think\db\Query; use think\Exception; use think\Loader; @@ -27,19 +27,19 @@ class BelongsToMany extends Relation protected $middle; // 中间表模型名称 protected $pivotName; - // 中间表模型对象 - protected $pivot; // 中间表数据名称 protected $pivotDataName = 'pivot'; + // 中间表模型对象 + protected $pivot; /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型外键 - * @param string $localKey 当前模型关联键 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 */ public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) { @@ -47,23 +47,22 @@ class BelongsToMany extends Relation $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; + if (false !== strpos($table, '\\')) { $this->pivotName = $table; $this->middle = basename(str_replace('\\', '/', $table)); } else { $this->middle = $table; } + $this->query = (new $model)->db(); $this->pivot = $this->newPivot(); - - if ('think\model\Pivot' == get_class($this->pivot)) { - $this->pivot->name($this->middle); - } } /** * 设置中间表模型 - * @param $pivot + * @access public + * @param $pivot * @return $this */ public function pivot($pivot) @@ -99,6 +98,7 @@ class BelongsToMany extends Relation /** * 实例化中间表模型 + * @access public * @param array $data * @param bool $isUpdate * @return Pivot @@ -108,94 +108,111 @@ class BelongsToMany extends Relation { $class = $this->pivotName ?: '\\think\\model\\Pivot'; $pivot = new $class($data, $this->parent, $this->middle); + if ($pivot instanceof Pivot) { return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; - } else { - throw new Exception('pivot model must extends: \think\model\Pivot'); } + + throw new Exception('pivot model must extends: \think\model\Pivot'); } /** * 合成中间表模型 - * @param array|Collection|Paginator $models + * @access protected + * @param array|Collection|Paginator $models */ protected function hydratePivot($models) { foreach ($models as $model) { $pivot = []; + foreach ($model->getData() as $key => $val) { if (strpos($key, '__')) { list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { $pivot[$attr] = $val; unset($model->$key); } } } + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); } } /** * 创建关联查询Query对象 + * @access protected * @return Query */ protected function buildQuery() { $foreignKey = $this->foreignKey; $localKey = $this->localKey; - $pk = $this->parent->getPk(); + // 关联查询 - $condition['pivot.' . $localKey] = $this->parent->$pk; + $pk = $this->parent->getPk(); + + $condition[] = ['pivot.' . $localKey, '=', $this->parent->$pk]; + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Collection */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); + if ($closure instanceof Closure) { + $closure($this->query); } + $result = $this->buildQuery()->relation($subRelation)->select(); $this->hydratePivot($result); + return $result; } /** * 重载select方法 - * @param null $data - * @return false|\PDOStatement|string|Collection + * @access public + * @param mixed $data + * @return Collection */ public function select($data = null) { $result = $this->buildQuery()->select($data); $this->hydratePivot($result); + return $result; } /** * 重载paginate方法 - * @param null $listRows - * @param bool $simple - * @param array $config + * @access public + * @param null $listRows + * @param bool $simple + * @param array $config * @return Paginator */ public function paginate($listRows = null, $simple = false, $config = []) { $result = $this->buildQuery()->paginate($listRows, $simple, $config); $this->hydratePivot($result); + return $result; } /** * 重载find方法 - * @param null $data - * @return array|false|\PDOStatement|string|Model + * @access public + * @param mixed $data + * @return Model */ public function find($data = null) { @@ -203,14 +220,15 @@ class BelongsToMany extends Relation if ($result) { $this->hydratePivot([$result]); } + return $result; } /** * 查找多条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model + * @param array|string|Query|\Closure $data + * @return Collection */ public function selectOrFail($data = null) { @@ -220,8 +238,8 @@ class BelongsToMany extends Relation /** * 查找单条记录 如果不存在则抛出异常 * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model + * @param array|string|Query|\Closure $data + * @return Model */ public function findOrFail($data = null) { @@ -231,10 +249,10 @@ class BelongsToMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -245,8 +263,8 @@ class BelongsToMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query * @throws Exception */ @@ -257,25 +275,25 @@ class BelongsToMany extends Relation /** * 设置中间表的查询条件 - * @param $field - * @param null $op - * @param null $condition + * @access public + * @param string $field + * @param string $op + * @param mixed $condition * @return $this */ public function wherePivot($field, $op = null, $condition = null) { - $field = 'pivot.' . $field; - $this->query->where($field, $op, $condition); + $this->query->where('pivot.' . $field, $op, $condition); return $this; } /** * 预载入关联查询(数据集) * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -295,13 +313,12 @@ class BelongsToMany extends Relation if (!empty($range)) { // 查询关联数据 $data = $this->eagerlyManyToMany([ - 'pivot.' . $localKey => [ - 'in', - $range, - ], - ], $relation, $subRelation); + ['pivot.' . $localKey, 'in', $range], + ], $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { @@ -316,24 +333,28 @@ class BelongsToMany extends Relation /** * 预载入关联查询(单个数据) * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); + if (isset($result->$pk)) { $pk = $result->$pk; // 查询管理数据 - $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $relation, $subRelation, $closure); // 关联数据封装 if (!isset($data[$pk])) { $data[$pk] = []; } + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); } } @@ -341,57 +362,81 @@ class BelongsToMany extends Relation /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') { - $pk = $result->getPk(); - $count = 0; - if (isset($result->$pk)) { - $pk = $result->$pk; - $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); - } - return $count; - } + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure instanceof Closure) { + $return = $closure($this->query); - /** - * 获取关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @param string $name 统计数据别名 - * @return string - */ - public function getRelationCountQuery($closure, &$name = null) - { - if ($closure) { - $return = call_user_func_array($closure, [ & $this->query]); if ($return && is_string($return)) { $name = $return; } } return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ - 'pivot.' . $this->localKey => [ - 'exp', - Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return array + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + [ + 'pivot.' . $this->localKey, 'exp', $this->query->raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), ], - ])->fetchSql()->count(); + ])->fetchSql()->$aggregate($field); } /** * 多对多 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 * @return array */ - protected function eagerlyManyToMany($where, $relation, $subRelation = '') + protected function eagerlyManyToMany($where, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 - $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->select(); // 组装模型数据 $data = []; @@ -406,18 +451,21 @@ class BelongsToMany extends Relation } } } + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + $data[$pivot[$this->localKey]][] = $set; } + return $data; } /** * BELONGS TO MANY 关联查询 - * @access public - * @param string $foreignKey 关联模型关联键 - * @param string $localKey 当前模型关联键 - * @param array $condition 关联查询条件 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 * @return Query */ protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) @@ -427,7 +475,8 @@ class BelongsToMany extends Relation $table = $this->pivot->getTable(); $fields = $this->getQueryFields($tableName); - $query = $this->query->field($fields) + $query = $this->query + ->field($fields) ->field(true, false, $table, 'pivot', 'pivot__'); if (empty($this->baseQuery)) { @@ -435,15 +484,16 @@ class BelongsToMany extends Relation $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) ->where($condition); } + return $query; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot */ public function save($data, array $pivot = []) { @@ -454,30 +504,33 @@ class BelongsToMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 - * @param array $pivot 中间表额外数据 - * @param bool $samePivot 额外数据是否相同 - * @return integer + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false */ public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) { - $result = false; + $result = []; + foreach ($dataSet as $key => $data) { if (!$samePivot) { $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; } else { $pivotData = $pivot; } - $result = $this->attach($data, $pivotData); + + $result[] = $this->attach($data, $pivotData); } - return $result; + + return empty($result) ? false : $result; } /** * 附加关联的一个中间表数据 * @access public - * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 - * @param array $pivot 中间表额外数据 + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 * @return array|Pivot * @throws Exception */ @@ -489,8 +542,7 @@ class BelongsToMany extends Relation } else { // 保存关联表数据 $model = new $this->model; - $model->save($data); - $id = $model->getLastInsID(); + $id = $model->insertGetId($data); } } elseif (is_numeric($data) || is_string($data)) { // 根据关联表主键直接写入中间表 @@ -506,15 +558,21 @@ class BelongsToMany extends Relation $pk = $this->parent->getPk(); $pivot[$this->localKey] = $this->parent->$pk; $ids = (array) $id; + foreach ($ids as $id) { $pivot[$this->foreignKey] = $id; - $this->pivot->insert($pivot, true); + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); $result[] = $this->newPivot($pivot, true); } + if (count($result) == 1) { // 返回中间表模型对象 $result = $result[0]; } + return $result; } else { throw new Exception('miss relation data'); @@ -525,21 +583,21 @@ class BelongsToMany extends Relation * 判断是否存在关联数据 * @access public * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 - * @return Pivot + * @return Pivot|false * @throws Exception */ public function attached($data) { if ($data instanceof Model) { - $relationFk = $data->getPk(); - $id = $data->$relationFk; + $id = $data->getKey(); } else { $id = $data; } - $pk = $this->parent->getPk(); - - $pivot = $this->pivot->where($this->localKey, $this->parent->$pk)->where($this->foreignKey, $id)->find(); + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->foreignKey, $id) + ->find(); return $pivot ?: false; } @@ -547,8 +605,8 @@ class BelongsToMany extends Relation /** * 解除关联的一个中间表数据 * @access public - * @param integer|array $data 数据 可以使用关联对象的主键 - * @param bool $relationDel 是否同时删除关联表数据 + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 * @return integer */ public function detach($data = null, $relationDel = false) @@ -563,24 +621,31 @@ class BelongsToMany extends Relation $relationFk = $data->getPk(); $id = $data->$relationFk; } + // 删除中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; + $pk = $this->parent->getPk(); + $pivot[] = [$this->localKey, '=', $this->parent->$pk]; + if (isset($id)) { - $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; } - $this->pivot->where($pivot)->delete(); + + $result = $this->pivot->where($pivot)->delete(); + // 删除关联表数据 if (isset($id) && $relationDel) { $model = $this->model; $model::destroy($id); } + + return $result; } /** * 数据同步 - * @param array $ids - * @param bool $detaching + * @access public + * @param array $ids + * @param bool $detaching * @return array */ public function sync($ids, $detaching = true) @@ -590,9 +655,13 @@ class BelongsToMany extends Relation 'detached' => [], 'updated' => [], ]; - $pk = $this->parent->getPk(); - $current = $this->pivot->where($this->localKey, $this->parent->$pk) + + $pk = $this->parent->getPk(); + + $current = $this->pivot + ->where($this->localKey, $this->parent->$pk) ->column($this->foreignKey); + $records = []; foreach ($ids as $key => $value) { @@ -607,7 +676,6 @@ class BelongsToMany extends Relation if ($detaching && count($detach) > 0) { $this->detach($detach); - $changes['detached'] = $detach; } @@ -615,19 +683,16 @@ class BelongsToMany extends Relation if (!in_array($id, $current)) { $this->attach($id, $attributes); $changes['attached'][] = $id; - } elseif (count($attributes) > 0 && - $this->attach($id, $attributes) - ) { + } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) { $changes['updated'][] = $id; } } return $changes; - } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ @@ -636,7 +701,10 @@ class BelongsToMany extends Relation if (empty($this->baseQuery) && $this->parent->getData()) { $pk = $this->parent->getPk(); $table = $this->pivot->getTable(); - $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + + $this->query + ->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk()) + ->where('pivot.' . $this->localKey, $this->parent->$pk); $this->baseQuery = true; } } diff --git a/Server/thinkphp/library/think/model/relation/HasMany.php b/Server/thinkphp/library/think/model/relation/HasMany.php index ebab051a..e4df5c4b 100644 --- a/Server/thinkphp/library/think/model/relation/HasMany.php +++ b/Server/thinkphp/library/think/model/relation/HasMany.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Loader; use think\Model; @@ -19,12 +20,12 @@ use think\model\Relation; class HasMany extends Relation { /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 */ public function __construct(Model $parent, $model, $foreignKey, $localKey) { @@ -33,20 +34,30 @@ class HasMany extends Relation $this->foreignKey = $foreignKey; $this->localKey = $localKey; $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); + if ($closure instanceof Closure) { + $closure($this->query); } - $list = $this->relation($subRelation)->select(); + + $list = $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select(); + $parent = clone $this->parent; foreach ($list as &$model) { @@ -58,17 +69,18 @@ class HasMany extends Relation /** * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $range = []; + foreach ($resultSet as $result) { // 获取关联外键列表 if (isset($result->$localKey)) { @@ -77,36 +89,37 @@ class HasMany extends Relation } if (!empty($range)) { - $data = $this->eagerlyOneToMany($this->query, [ - $this->foreignKey => [ - 'in', - $range, - ], - ], $relation, $subRelation, $closure); + $where = [ + [$this->foreignKey, 'in', $range], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; } - foreach ($data[$result->$localKey] as &$relationModel) { + foreach ($data[$pk] as &$relationModel) { $relationModel->setParent(clone $result); } - $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); + $result->setRelation($attr, $this->resultSetBuild($data[$pk])); } } } /** * 预载入关联查询 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) @@ -114,103 +127,124 @@ class HasMany extends Relation $localKey = $this->localKey; if (isset($result->$localKey)) { - $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + $pk = $result->$localKey; + $where = [ + [$this->foreignKey, '=', $pk], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + // 关联数据封装 - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; + if (!isset($data[$pk])) { + $data[$pk] = []; } - foreach ($data[$result->$localKey] as &$relationModel) { + foreach ($data[$pk] as &$relationModel) { $relationModel->setParent(clone $result); } - $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') { $localKey = $this->localKey; - $count = 0; - if (isset($result->$localKey)) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $count = $this->query->where($this->foreignKey, $result->$localKey)->count(); + + if (!isset($result->$localKey)) { + return 0; } - return $count; + + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); } /** * 创建关联统计子查询 * @access public - * @param \Closure $closure 闭包 - * @param string $name 统计数据别名 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 * @return string */ - public function getRelationCountQuery($closure, &$name = null) + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') { - if ($closure) { - $return = call_user_func_array($closure, [ & $this->query]); + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { - $name = $return; + $aggregateAlias = $return; } } - $localKey = $this->localKey ?: $this->parent->getPk(); - return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); + + return $this->query->alias($aggregate . '_table') + ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); } /** * 一对多 关联模型预查询 * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool $closure + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure * @return array */ - protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = null) { $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); + if ($closure instanceof Closure) { + $closure($this->query); } - $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); + + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; + foreach ($list as $set) { $data[$set->$foreignKey][] = $set; } + return $data; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param boolean $replace 是否自动识别更新和写入 * @return Model|false */ - public function save($data) + public function save($data, $replace = true) { - if ($data instanceof Model) { - $data = $data->getData(); - } + $model = $this->make(); - // 保存关联表数据 - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - - $model = new $this->model(); - return $model->save($data) ? $model : false; + return $model->replace($replace)->save($data) ? $model : false; } /** @@ -233,37 +267,44 @@ class HasMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 - * @return integer + * @param array|\think\Collection $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false */ - public function saveAll(array $dataSet) + public function saveAll($dataSet, $replace = true) { - $result = false; + $result = []; + foreach ($dataSet as $key => $data) { - $result = $this->save($data); + $result[] = $this->save($data, $replace); } - return $result; + + return empty($result) ? false : $result; } /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { - $table = $this->query->getTable(); - $model = basename(str_replace('\\', '/', get_class($this->parent))); - $relation = basename(str_replace('\\', '/', $this->model)); + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $softDelete = $this->query->getOptions('soft_delete'); return $this->parent->db() ->alias($model) ->field($model . '.*') ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->group($relation . '.' . $this->foreignKey) ->having('count(' . $id . ')' . $operator . $count); } @@ -271,7 +312,7 @@ class HasMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $where 查询条件(数组或者闭包) * @param mixed $fields 字段 * @return Query */ @@ -282,25 +323,25 @@ class HasMany extends Relation $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } + $this->getQueryWhere($where, $relation); } - $fields = $this->getRelationQueryFields($fields, $model); + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); - return $this->parent->db()->alias($model) - ->field($fields) + return $this->parent->db() + ->alias($model) ->group($model . '.' . $this->localKey) + ->field($fields) ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->where($where); } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ @@ -309,8 +350,9 @@ class HasMany extends Relation if (empty($this->baseQuery)) { if (isset($this->parent->{$this->localKey})) { // 关联查询带入关联条件 - $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); } + $this->baseQuery = true; } } diff --git a/Server/thinkphp/library/think/model/relation/HasManyThrough.php b/Server/thinkphp/library/think/model/relation/HasManyThrough.php index 3a9a5482..be0b0cd9 100644 --- a/Server/thinkphp/library/think/model/relation/HasManyThrough.php +++ b/Server/thinkphp/library/think/model/relation/HasManyThrough.php @@ -11,8 +11,8 @@ namespace think\model\relation; +use Closure; use think\db\Query; -use think\Exception; use think\Loader; use think\Model; use think\model\Relation; @@ -25,131 +25,337 @@ class HasManyThrough extends Relation protected $through; /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $foreignKey 关联外键 - * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 + * 中间主键 + * @var string + */ + protected $throughPk; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 */ public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) { $this->parent = $parent; $this->model = $model; - $this->through = $through; + $this->through = (new $through)->db(); $this->foreignKey = $foreignKey; $this->throughKey = $throughKey; + $this->throughPk = $this->through->getPk(); $this->localKey = $localKey; $this->query = (new $model)->db(); } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); + if ($closure instanceof Closure) { + $closure($this->query); } - return $this->relation($subRelation)->select(); + $this->baseQuery(); + + return $this->query->relation($subRelation)->select(); } /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { - return $this->parent; + $model = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $relation = (new $this->model)->db(); + $relationTable = $relation->getTable(); + $softDelete = $this->query->getOptions('soft_delete'); + + if ('*' != $id) { + $id = $relationTable . '.' . $relation->getPk(); + } + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $relationTable) { + $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relationTable . '.' . $this->throughKey) + ->having('count(' . $id . ')' . $operator . $count); } /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) { - throw new Exception('relation not support: hasWhere'); + $model = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = (new $this->model)->db()->getTable(); + + if (is_array($where)) { + $this->getQueryWhere($where, $modelTable); + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $modelTable) { + $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($modelTable . '.' . $this->throughKey) + ->where($where) + ->field($fields); } /** - * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param mixed $subRelation 子关联名 + * @param Closure $closure 闭包 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - {} + public function eagerlyResultSet(array &$resultSet, $relation, $subRelation = '', $closure = null) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + // 设置关联属性 + $result->setRelation($attr, $this->resultSetBuild($data[$pk])); + } + } + } /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param mixed $subRelation 子关联名 + * @param Closure $closure 闭包 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - {} + public function eagerlyResult($result, $relation, $subRelation = '', $closure = null) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $pk = $result->$localKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $pk], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param mixed $subRelation 子关联 + * @param Closure $closure + * @return array + */ + protected function eagerlyWhere(array $where, $key, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + $throughList = $this->through->where($where)->select(); + $keys = $throughList->column($this->throughPk, $this->throughPk); + + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->query->where($this->throughKey, 'in', $keys)->select(); + + // 组装模型数据 + $data = []; + $keys = $throughList->column($this->foreignKey, $this->throughPk); + + foreach ($list as $set) { + $data[$keys[$set->{$this->throughKey}]][] = $set; + } + + return $data; + } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 * @return integer */ - public function relationCount($result, $closure) - {} + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $result->$localKey) + ->$aggregate($field); + } /** * 创建关联统计子查询 * @access public - * @param \Closure $closure 闭包 - * @param string $name 统计数据别名 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 * @return string */ - public function getRelationCountQuery($closure, &$name = null) + public function getRelationCountQuery($closure = null, $aggregate = 'count', $field = '*', &$name = null) { - throw new Exception('relation not support: withCount'); + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ protected function baseQuery() { if (empty($this->baseQuery) && $this->parent->getData()) { - $through = $this->through; $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); - $throughTable = $through::getTable(); - $pk = (new $through)->getPk(); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; $throughKey = $this->throughKey; $modelTable = $this->parent->getTable(); - $this->query->field($alias . '.*')->alias($alias) + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->alias($alias) ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + $this->baseQuery = true; } } diff --git a/Server/thinkphp/library/think/model/relation/HasOne.php b/Server/thinkphp/library/think/model/relation/HasOne.php index db74e4a6..fe09443c 100644 --- a/Server/thinkphp/library/think/model/relation/HasOne.php +++ b/Server/thinkphp/library/think/model/relation/HasOne.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Loader; use think\Model; @@ -18,37 +19,42 @@ use think\Model; class HasOne extends OneToOne { /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @param string $joinType JOIN类型 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + public function __construct(Model $parent, $model, $foreignKey, $localKey) { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->joinType = $joinType; + $this->joinType = 'INNER'; $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return array|false|\PDOStatement|string|Model + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { - // 执行关联定义方法 $localKey = $this->localKey; - if ($closure) { - call_user_func_array($closure, [ & $this->query]); + + if ($closure instanceof Closure) { + $closure($this->query); } + // 判断关联类型执行查询 $relationModel = $this->query ->removeWhereField($this->foreignKey) @@ -63,30 +69,96 @@ class HasOne extends OneToOne return $relationModel; } + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + /** * 根据关联条件查询当前模型 * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ - public function has() + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { $table = $this->query->getTable(); $model = basename(str_replace('\\', '/', get_class($this->parent))); $relation = basename(str_replace('\\', '/', $this->model)); $localKey = $this->localKey; $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + return $this->parent->db() ->alias($model) - ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { - $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); }); } /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -96,28 +168,29 @@ class HasOne extends OneToOne $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } + $this->getQueryWhere($where, $relation); } - $fields = $this->getRelationQueryFields($fields, $model); - return $this->parent->db()->alias($model) + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) ->field($fields) ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->where($where); } /** * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) @@ -135,14 +208,14 @@ class HasOne extends OneToOne if (!empty($range)) { $this->query->removeWhereField($foreignKey); - $data = $this->eagerlyWhere($this->query, [ - $foreignKey => [ - 'in', - $range, - ], + + $data = $this->eagerlyWhere([ + [$foreignKey, 'in', $range], ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { // 关联模型 @@ -153,6 +226,7 @@ class HasOne extends OneToOne $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + if (!empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); @@ -166,19 +240,23 @@ class HasOne extends OneToOne /** * 预载入关联查询(数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; + $this->query->removeWhereField($foreignKey); - $data = $this->eagerlyWhere($this->query, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $relation, $subRelation, $closure); // 关联模型 if (!isset($data[$result->$localKey])) { @@ -188,6 +266,7 @@ class HasOne extends OneToOne $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + if (!empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); diff --git a/Server/thinkphp/library/think/model/relation/MorphMany.php b/Server/thinkphp/library/think/model/relation/MorphMany.php index 2755d575..d2af66e9 100644 --- a/Server/thinkphp/library/think/model/relation/MorphMany.php +++ b/Server/thinkphp/library/think/model/relation/MorphMany.php @@ -11,7 +11,7 @@ namespace think\model\relation; -use think\Db; +use Closure; use think\db\Query; use think\Exception; use think\Loader; @@ -27,13 +27,13 @@ class MorphMany extends Relation protected $type; /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $morphKey 关联外键 - * @param string $morphType 多态字段名 - * @param string $type 多态类型 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 */ public function __construct(Model $parent, $model, $morphKey, $morphType, $type) { @@ -47,16 +47,20 @@ class MorphMany extends Relation /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); + if ($closure instanceof Closure) { + $closure($this->query); } - $list = $this->relation($subRelation)->select(); + + $this->baseQuery(); + + $list = $this->query->relation($subRelation)->select(); $parent = clone $this->parent; foreach ($list as &$model) { @@ -69,10 +73,10 @@ class MorphMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -83,8 +87,8 @@ class MorphMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -95,10 +99,10 @@ class MorphMany extends Relation /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -107,6 +111,7 @@ class MorphMany extends Relation $morphKey = $this->morphKey; $type = $this->type; $range = []; + foreach ($resultSet as $result) { $pk = $result->getPk(); // 获取关联外键列表 @@ -116,21 +121,26 @@ class MorphMany extends Relation } if (!empty($range)) { - $data = $this->eagerlyMorphToMany([ - $morphKey => ['in', $range], - $morphType => $type, - ], $relation, $subRelation, $closure); + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { $data[$result->$pk] = []; } + foreach ($data[$result->$pk] as &$relationModel) { $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); } } @@ -139,125 +149,138 @@ class MorphMany extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); - if (isset($result->$pk)) { - $data = $this->eagerlyMorphToMany([ - $this->morphKey => $result->$pk, - $this->morphType => $this->type, - ], $relation, $subRelation, $closure); - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; + if (isset($result->$pk)) { + $key = $result->$pk; + $where = [ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + + if (!isset($data[$key])) { + $data[$key] = []; } - foreach ($data[$result->$pk] as &$relationModel) { + foreach ($data[$key] as &$relationModel) { $relationModel->setParent(clone $result); $relationModel->isUpdate(true); } - $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$key])); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 * @return integer */ - public function relationCount($result, $closure) + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') { - $pk = $result->getPk(); - $count = 0; - if (isset($result->$pk)) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); - } - return $count; - } + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); - /** - * 创建关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @param string $name 统计数据别名 - * @return string - */ - public function getRelationCountQuery($closure, &$name = null) - { - if ($closure) { - $return = call_user_func_array($closure, [ & $this->query]); if ($return && is_string($return)) { $name = $return; } } - return $this->query->where([ - $this->morphKey => [ - 'exp', - Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), - ], - $this->morphType => $this->type, - ])->fetchSql()->count(); + return $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) + ->fetchSql() + ->$aggregate($field); } /** * 多态一对多 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 * @return array */ - protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $this]); + $this->query->removeOption('where'); + + if ($closure instanceof Closure) { + $closure($this->query); } + $list = $this->query->where($where)->with($subRelation)->select(); $morphKey = $this->morphKey; + // 组装模型数据 $data = []; foreach ($list as $set) { $data[$set->$morphKey][] = $set; } + return $data; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 * @return Model|false */ public function save($data) { - if ($data instanceof Model) { - $data = $data->getData(); - } + $model = $this->make(); - // 保存关联表数据 - $pk = $this->parent->getPk(); - - $data[$this->morphKey] = $this->parent->$pk; - $data[$this->morphType] = $this->type; - - $model = new $this->model(); - - return $model->save() ? $model : false; + return $model->save($data) ? $model : false; } /** @@ -283,30 +306,35 @@ class MorphMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 - * @return integer + * @param array $dataSet 数据集 + * @return array|false */ public function saveAll(array $dataSet) { - $result = false; + $result = []; + foreach ($dataSet as $key => $data) { - $result = $this->save($data); + $result[] = $this->save($data); } - return $result; + + return empty($result) ? false : $result; } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ protected function baseQuery() { if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $map[$this->morphKey] = $this->parent->$pk; - $map[$this->morphType] = $this->type; - $this->query->where($map); + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; } } diff --git a/Server/thinkphp/library/think/model/relation/MorphOne.php b/Server/thinkphp/library/think/model/relation/MorphOne.php index 5ec71724..6bc205c5 100644 --- a/Server/thinkphp/library/think/model/relation/MorphOne.php +++ b/Server/thinkphp/library/think/model/relation/MorphOne.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Exception; use think\Loader; @@ -28,11 +29,11 @@ class MorphOne extends Relation /** * 构造函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $morphKey 关联外键 - * @param string $morphType 多态字段名 - * @param string $type 多态类型 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 */ public function __construct(Model $parent, $model, $morphKey, $morphType, $type) { @@ -46,16 +47,20 @@ class MorphOne extends Relation /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); + if ($closure instanceof Closure) { + $closure($this->query); } - $relationModel = $this->relation($subRelation)->find(); + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); if ($relationModel) { $relationModel->setParent(clone $this->parent); @@ -67,10 +72,10 @@ class MorphOne extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -81,8 +86,8 @@ class MorphOne extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -93,10 +98,10 @@ class MorphOne extends Relation /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) @@ -105,6 +110,7 @@ class MorphOne extends Relation $morphKey = $this->morphKey; $type = $this->type; $range = []; + foreach ($resultSet as $result) { $pk = $result->getPk(); // 获取关联外键列表 @@ -115,11 +121,13 @@ class MorphOne extends Relation if (!empty($range)) { $data = $this->eagerlyMorphToOne([ - $morphKey => ['in', $range], - $morphType => $type, + [$morphKey, 'in', $range], + [$morphType, '=', $type], ], $relation, $subRelation, $closure); + // 关联属性名 $attr = Loader::parseName($relation); + // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { @@ -138,20 +146,21 @@ class MorphOne extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); + if (isset($result->$pk)) { $pk = $result->$pk; $data = $this->eagerlyMorphToOne([ - $this->morphKey => $pk, - $this->morphType => $this->type, + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], ], $relation, $subRelation, $closure); if (isset($data[$pk])) { @@ -168,50 +177,43 @@ class MorphOne extends Relation /** * 多态一对一 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 * @return array */ - protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $this]); + if ($closure instanceof Closure) { + $closure($this->query); } - $list = $this->query->where($where)->with($subRelation)->find(); + + $list = $this->query->where($where)->with($subRelation)->select(); $morphKey = $this->morphKey; + // 组装模型数据 $data = []; + foreach ($list as $set) { - $data[$set->$morphKey][] = $set; + $data[$set->$morphKey] = $set; } + return $data; } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 * @return Model|false */ public function save($data) { - if ($data instanceof Model) { - $data = $data->getData(); - } - - // 保存关联表数据 - $pk = $this->parent->getPk(); - - $data[$this->morphKey] = $this->parent->$pk; - $data[$this->morphType] = $this->type; - - $model = new $this->model(); - - return $model->save() ? $model : false; + $model = $this->make(); + return $model->save($data) ? $model : false; } /** @@ -224,6 +226,7 @@ class MorphOne extends Relation if ($data instanceof Model) { $data = $data->getData(); } + // 保存关联表数据 $pk = $this->parent->getPk(); @@ -241,23 +244,14 @@ class MorphOne extends Relation protected function baseQuery() { if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $map[$this->morphKey] = $this->parent->$pk; - $map[$this->morphType] = $this->type; - $this->query->where($map); + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); $this->baseQuery = true; } } - /** - * 创建关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @param string $name 统计数据别名 - * @return string - */ - public function getRelationCountQuery($closure, &$name = null) - { - throw new Exception('relation not support: withCount'); - } } diff --git a/Server/thinkphp/library/think/model/relation/MorphTo.php b/Server/thinkphp/library/think/model/relation/MorphTo.php index 7d452651..0786c2fe 100644 --- a/Server/thinkphp/library/think/model/relation/MorphTo.php +++ b/Server/thinkphp/library/think/model/relation/MorphTo.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\Exception; use think\Loader; use think\Model; @@ -23,16 +24,17 @@ class MorphTo extends Relation protected $morphType; // 多态别名 protected $alias; + // 关联名 protected $relation; /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $morphType 多态字段名 - * @param string $morphKey 外键名 - * @param array $alias 多态别名定义 - * @param string $relation 关联名 + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 */ public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) { @@ -52,38 +54,44 @@ class MorphTo extends Relation { $morphType = $this->morphType; $model = $this->parseModel($this->parent->$morphType); + return (new $model); } /** * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return mixed + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model */ public function getRelation($subRelation = '', $closure = null) { $morphKey = $this->morphKey; $morphType = $this->morphType; + // 多态模型 $model = $this->parseModel($this->parent->$morphType); + // 主键数据 - $pk = $this->parent->$morphKey; + $pk = $this->parent->$morphKey; + $relationModel = (new $model)->relation($subRelation)->find($pk); if ($relationModel) { $relationModel->setParent(clone $this->parent); } + return $relationModel; } /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') @@ -94,8 +102,8 @@ class MorphTo extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -106,7 +114,7 @@ class MorphTo extends Relation /** * 解析模型的完整命名空间 * @access protected - * @param string $model 模型名(或者完整类名) + * @param string $model 模型名(或者完整类名) * @return string */ protected function parseModel($model) @@ -114,24 +122,27 @@ class MorphTo extends Relation if (isset($this->alias[$model])) { $model = $this->alias[$model]; } + if (false === strpos($model, '\\')) { $path = explode('\\', get_class($this->parent)); array_pop($path); array_push($path, Loader::parseName($model, 1)); $model = implode('\\', $path); } + return $model; } /** * 设置多态别名 * @access public - * @param array $alias 别名定义 + * @param array $alias 别名定义 * @return $this */ public function setAlias($alias) { $this->alias = $alias; + return $this; } @@ -148,10 +159,10 @@ class MorphTo extends Relation /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void * @throws Exception */ @@ -160,6 +171,7 @@ class MorphTo extends Relation $morphKey = $this->morphKey; $morphType = $this->morphType; $range = []; + foreach ($resultSet as $result) { // 获取关联外键列表 if (!empty($result->$morphKey)) { @@ -170,28 +182,39 @@ class MorphTo extends Relation if (!empty($range)) { // 关联属性名 $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { // 多态类型映射 $model = $this->parseModel($key); - $obj = new $model; + $obj = (new $model)->db(); $pk = $obj->getPk(); + // 预载入关联查询 支持嵌套预载入 + if ($closure instanceof \Closure) { + $closure($obj); + + if ($field = $obj->getOptions('with_field')) { + $obj->field($field)->removeOption('with_field'); + } + } $list = $obj->all($val, $subRelation); $data = []; + foreach ($list as $k => $vo) { $data[$vo->$pk] = $vo; } + foreach ($resultSet as $result) { if ($key == $result->$morphType) { // 关联模型 if (!isset($data[$result->$morphKey])) { - throw new Exception('relation data not exists :' . $this->model); + $relationModel = null; } else { $relationModel = $data[$result->$morphKey]; $relationModel->setParent(clone $result); $relationModel->isUpdate(true); - - $result->setRelation($attr, $relationModel); } + + $result->setRelation($attr, $relationModel); } } } @@ -201,10 +224,10 @@ class MorphTo extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure) @@ -213,27 +236,30 @@ class MorphTo extends Relation $morphType = $this->morphType; // 多态类型映射 $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 * @return integer */ - public function relationCount($result, $closure) - { - } + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + {} /** * 多态MorphTo 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param string $relation 关联名 - * @param $result - * @param string $subRelation 子关联 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param string $subRelation 子关联 * @return void */ protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') @@ -241,18 +267,20 @@ class MorphTo extends Relation // 预载入关联查询 支持嵌套预载入 $pk = $this->parent->{$this->morphKey}; $data = (new $model)->with($subRelation)->find($pk); + if ($data) { $data->setParent(clone $result); $data->isUpdate(true); } + $result->setRelation(Loader::parseName($relation), $data ?: null); } /** * 添加关联数据 * @access public - * @param Model $model 关联模型对象 - * @param string $type 多态类型 + * @param Model $model 关联模型对象 + * @param string $type 多态类型 * @return Model */ public function associate($model, $type = '') @@ -285,15 +313,4 @@ class MorphTo extends Relation return $this->parent->setRelation($this->relation, null); } - /** - * 创建关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @param string $name 统计数据别名 - * @return string - */ - public function getRelationCountQuery($closure, &$name = null) - { - throw new Exception('relation not support: withCount'); - } } diff --git a/Server/thinkphp/library/think/model/relation/OneToOne.php b/Server/thinkphp/library/think/model/relation/OneToOne.php index 353ce21b..5e22b800 100644 --- a/Server/thinkphp/library/think/model/relation/OneToOne.php +++ b/Server/thinkphp/library/think/model/relation/OneToOne.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Exception; use think\Loader; @@ -30,13 +31,13 @@ abstract class OneToOne extends Relation protected $joinType; // 要绑定的属性 protected $bindAttr = []; - // 关联方法名 + // 关联名 protected $relation; /** * 设置join类型 * @access public - * @param string $type JOIN类型 + * @param string $type JOIN类型 * @return $this */ public function joinType($type) @@ -48,72 +49,78 @@ abstract class OneToOne extends Relation /** * 预载入关联查询(JOIN方式) * @access public - * @param Query $query 查询对象 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param \Closure $closure 闭包条件 - * @param bool $first + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param mixed $field 关联字段 + * @param string $joinType JOIN方式 + * @param \Closure $closure 闭包条件 + * @param bool $first * @return void */ - public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + public function eagerly(Query $query, $relation, $field, $joinType, $closure, $first) { - $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); if ($first) { $table = $query->getTable(); $query->table([$table => $name]); + if ($query->getOptions('field')) { - $field = $query->getOptions('field'); + $masterField = $query->getOptions('field'); $query->removeOption('field'); } else { - $field = true; + $masterField = true; } - $query->field($field, false, $table, $name); - $field = null; + + $query->field($masterField, false, $table, $name); } // 预载入封装 $joinTable = $this->query->getTable(); $joinAlias = $relation; + $joinType = $joinType ?: $this->joinType; + $query->via($joinAlias); if ($this instanceof BelongsTo) { - $query->join([$joinTable => $joinAlias], $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + $joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey; } else { - $query->join([$joinTable => $joinAlias], $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey; } - if ($closure) { + if ($closure instanceof Closure) { // 执行闭包查询 - call_user_func_array($closure, [ & $query]); + $closure($query); // 使用withField指定获取关联的字段,如 // $query->where(['id'=>1])->withField('id,name'); if ($query->getOptions('with_field')) { $field = $query->getOptions('with_field'); $query->removeOption('with_field'); } - } elseif (isset($this->option['field'])) { - $field = $this->option['field']; } - $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); + + $query->join([$joinTable => $joinAlias], $joinOn, $joinType) + ->field($field, false, $joinTable, $joinAlias, $relation . '__'); } /** * 预载入关联查询(数据集) - * @param array $resultSet - * @param string $relation - * @param string $subRelation - * @param \Closure $closure + * @access protected + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure * @return mixed */ abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); /** * 预载入关联查询(数据) - * @param Model $result - * @param string $relation - * @param string $subRelation - * @param \Closure $closure + * @access protected + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure * @return mixed */ abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); @@ -121,49 +128,51 @@ abstract class OneToOne extends Relation /** * 预载入关联查询(数据集) * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param bool $join 是否为JOIN方式 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $join = false) { - if (1 == $this->eagerlyType) { - // IN查询 - $this->eagerlySet($resultSet, $relation, $subRelation, $closure); - } else { - // 模型关联组装 + if ($join || 0 == $this->eagerlyType) { + // 模型JOIN关联组装 foreach ($resultSet as $result) { $this->match($this->model, $relation, $result); } + } else { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); } } /** * 预载入关联查询(数据) * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param bool $join 是否为JOIN方式 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $join = false) { - if (1 == $this->eagerlyType) { + if (0 == $this->eagerlyType || $join) { + // 模型JOIN关联组装 + $this->match($this->model, $relation, $result); + } else { // IN查询 $this->eagerlyOne($result, $relation, $subRelation, $closure); - } else { - // 模型关联组装 - $this->match($this->model, $relation, $result); } } /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return Model|false */ public function save($data) @@ -171,21 +180,24 @@ abstract class OneToOne extends Relation if ($data instanceof Model) { $data = $data->getData(); } + $model = new $this->model; // 保存关联表数据 $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; } /** * 设置预载入方式 * @access public - * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 * @return $this */ public function setEagerlyType($type) { $this->eagerlyType = $type; + return $this; } @@ -202,7 +214,7 @@ abstract class OneToOne extends Relation /** * 绑定关联表的属性到父模型属性 * @access public - * @param mixed $attr 要绑定的属性列表 + * @param mixed $attr 要绑定的属性列表 * @return $this */ public function bind($attr) @@ -211,6 +223,7 @@ abstract class OneToOne extends Relation $attr = explode(',', $attr); } $this->bindAttr = $attr; + return $this; } @@ -224,23 +237,12 @@ abstract class OneToOne extends Relation return $this->bindAttr; } - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @return integer - */ - public function relationCount($result, $closure) - { - } - /** * 一对一 关联模型预查询拼装 * @access public - * @param string $model 模型名称 - * @param string $relation 关联名 - * @param Model $result 模型对象实例 + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 * @return void */ protected function match($model, $relation, &$result) @@ -257,9 +259,15 @@ abstract class OneToOne extends Relation } if (isset($list[$relation])) { - $relationModel = new $model($list[$relation]); - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); + $array = array_unique($list[$relation]); + + if (count($array) == 1 && null === current($array)) { + $relationModel = null; + } else { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } if (!empty($this->bindAttr)) { $this->bindAttr($relationModel, $result, $this->bindAttr); @@ -267,71 +275,63 @@ abstract class OneToOne extends Relation } else { $relationModel = null; } + $result->setRelation(Loader::parseName($relation), $relationModel); } /** * 绑定关联属性到父模型 * @access protected - * @param Model $model 关联模型对象 - * @param Model $result 父模型对象 - * @param array $bindAttr 绑定属性 + * @param Model $result 关联模型对象 + * @param Model $model 父模型对象 * @return void * @throws Exception */ - protected function bindAttr($model, &$result, $bindAttr) + protected function bindAttr($model, &$result) { - foreach ($bindAttr as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($result->$key)) { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { throw new Exception('bind attr has exists:' . $key); - } else { - $result->setAttr($key, $model ? $model->$attr : null); } + + $result->setAttr($key, $model ? $model->getAttr($attr) : null); } } /** * 一对一 关联模型预查询(IN方式) * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $key 关联键名 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure * @return array */ - protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = null) { - $this->baseQuery = true; - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); - if ($field = $model->getOptions('with_field')) { - $model->field($field)->removeOption('with_field'); + if ($closure instanceof Closure) { + $closure($this->query); + + if ($field = $this->query->getOptions('with_field')) { + $this->query->field($field)->removeOption('with_field'); } } - $list = $model->where($where)->with($subRelation)->select(); + + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; + foreach ($list as $set) { $data[$set->$key] = $set; } + return $data; } - /** - * 创建关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @param string $name 统计数据别名 - * @return string - */ - public function getRelationCountQuery($closure, &$name = null) - { - throw new Exception('relation not support: withCount'); - } } diff --git a/Server/thinkphp/library/think/paginator/driver/Bootstrap.php b/Server/thinkphp/library/think/paginator/driver/Bootstrap.php index c5ac60db..ab5315c0 100644 --- a/Server/thinkphp/library/think/paginator/driver/Bootstrap.php +++ b/Server/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -57,13 +57,14 @@ class Bootstrap extends Paginator */ protected function getLinks() { - if ($this->simple) + if ($this->simple) { return ''; + } $block = [ 'first' => null, 'slider' => null, - 'last' => null + 'last' => null, ]; $side = 3; @@ -196,7 +197,7 @@ class Bootstrap extends Paginator */ protected function getPageLinkWrapper($url, $page) { - if ($page == $this->currentPage()) { + if ($this->currentPage() == $page) { return $this->getActivePageWrapper($page); } diff --git a/Server/thinkphp/library/think/response/Json.php b/Server/thinkphp/library/think/response/Json.php index c906bfc1..aa5bbd6f 100644 --- a/Server/thinkphp/library/think/response/Json.php +++ b/Server/thinkphp/library/think/response/Json.php @@ -25,7 +25,7 @@ class Json extends Response /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed * @throws \Exception */ @@ -35,7 +35,7 @@ class Json extends Response // 返回JSON数据格式到客户端 包含状态信息 $data = json_encode($data, $this->options['json_encode_param']); - if ($data === false) { + if (false === $data) { throw new \InvalidArgumentException(json_last_error_msg()); } diff --git a/Server/thinkphp/library/think/response/Jsonp.php b/Server/thinkphp/library/think/response/Jsonp.php index 404bacbe..f69e88e1 100644 --- a/Server/thinkphp/library/think/response/Jsonp.php +++ b/Server/thinkphp/library/think/response/Jsonp.php @@ -11,7 +11,6 @@ namespace think\response; -use think\Request; use think\Response; class Jsonp extends Response @@ -28,7 +27,7 @@ class Jsonp extends Response /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed * @throws \Exception */ @@ -36,16 +35,17 @@ class Jsonp extends Response { try { // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] - $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $var_jsonp_handler = $this->app['request']->param($this->options['var_jsonp_handler'], ""); $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; $data = json_encode($data, $this->options['json_encode_param']); - if ($data === false) { + if (false === $data) { throw new \InvalidArgumentException(json_last_error_msg()); } $data = $handler . '(' . $data . ');'; + return $data; } catch (\Exception $e) { if ($e->getPrevious()) { diff --git a/Server/thinkphp/library/think/response/Redirect.php b/Server/thinkphp/library/think/response/Redirect.php index 91694cb2..6b4f118a 100644 --- a/Server/thinkphp/library/think/response/Redirect.php +++ b/Server/thinkphp/library/think/response/Redirect.php @@ -11,10 +11,7 @@ namespace think\response; -use think\Request; use think\Response; -use think\Session; -use think\Url; class Redirect extends Response { @@ -27,42 +24,48 @@ class Redirect extends Response public function __construct($data = '', $code = 302, array $header = [], array $options = []) { parent::__construct($data, $code, $header, $options); + $this->cacheControl('no-cache,must-revalidate'); } /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) { $this->header['Location'] = $this->getTargetUrl(); + return; } /** * 重定向传值(通过Session) * @access protected - * @param string|array $name 变量名或者数组 - * @param mixed $value 值 + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 * @return $this */ public function with($name, $value = null) { + $session = $this->app['session']; + if (is_array($name)) { foreach ($name as $key => $val) { - Session::flash($key, $val); + $session->flash($key, $val); } } else { - Session::flash($name, $value); + $session->flash($name, $value); } + return $this; } /** * 获取跳转地址 + * @access public * @return string */ public function getTargetUrl() @@ -70,36 +73,47 @@ class Redirect extends Response if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { return $this->data; } else { - return Url::build($this->data, $this->params); + return $this->app['url']->build($this->data, $this->params); } } public function params($params = []) { $this->params = $params; + return $this; } /** * 记住当前url后跳转 + * @access public + * @param string $url 指定记住的url * @return $this */ - public function remember() + public function remember($url = null) { - Session::set('redirect_url', Request::instance()->url()); + $this->app['session']->set('redirect_url', $url ?: $this->app['request']->url()); + return $this; } /** * 跳转到上次记住的url + * @access public + * @param string $url 闪存数据不存在时的跳转地址 * @return $this */ - public function restore() + public function restore($url = null) { - if (Session::has('redirect_url')) { - $this->data = Session::get('redirect_url'); - Session::delete('redirect_url'); + $session = $this->app['session']; + + if ($session->has('redirect_url')) { + $this->data = $session->get('redirect_url'); + $session->delete('redirect_url'); + } elseif ($url) { + $this->data = $url; } + return $this; } } diff --git a/Server/thinkphp/library/think/response/View.php b/Server/thinkphp/library/think/response/View.php index 48f944a7..3d54c735 100644 --- a/Server/thinkphp/library/think/response/View.php +++ b/Server/thinkphp/library/think/response/View.php @@ -11,35 +11,53 @@ namespace think\response; -use think\Config; use think\Response; -use think\View as ViewTemplate; class View extends Response { // 输出参数 - protected $options = []; - protected $vars = []; - protected $replace = []; + protected $options = []; + protected $vars = []; + protected $config = []; + protected $filter; protected $contentType = 'text/html'; + /** + * 是否内容渲染 + * @var bool + */ + protected $isContent = false; + /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) { // 渲染模板输出 - return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) - ->fetch($data, $this->vars, $this->replace); + return $this->app['view'] + ->filter($this->filter) + ->fetch($data, $this->vars, $this->config, $this->isContent); + } + + /** + * 设置是否为内容渲染 + * @access public + * @param bool $content + * @return $this + */ + public function isContent($content = true) + { + $this->isContent = $content; + return $this; } /** * 获取视图变量 * @access public - * @param string $name 模板变量 + * @param string $name 模板变量 * @return mixed */ public function getVars($name = null) @@ -54,36 +72,48 @@ class View extends Response /** * 模板变量赋值 * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 + * @param mixed $name 变量名 + * @param mixed $value 变量值 * @return $this */ public function assign($name, $value = '') { if (is_array($name)) { $this->vars = array_merge($this->vars, $name); - return $this; } else { $this->vars[$name] = $value; } + + return $this; + } + + public function config($config) + { + $this->config = $config; return $this; } /** - * 视图内容替换 + * 视图内容过滤 * @access public - * @param string|array $content 被替换内容(支持批量替换) - * @param string $replace 替换内容 + * @param callable $filter * @return $this */ - public function replace($content, $replace = '') + public function filter($filter) { - if (is_array($content)) { - $this->replace = array_merge($this->replace, $content); - } else { - $this->replace[$content] = $replace; - } + $this->filter = $filter; return $this; } + /** + * 检查模板是否存在 + * @access private + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->app['view']->exists($name); + } + } diff --git a/Server/thinkphp/library/think/response/Xml.php b/Server/thinkphp/library/think/response/Xml.php index 3bdc052a..9c1681a4 100644 --- a/Server/thinkphp/library/think/response/Xml.php +++ b/Server/thinkphp/library/think/response/Xml.php @@ -36,23 +36,33 @@ class Xml extends Response /** * 处理数据 * @access protected - * @param mixed $data 要处理的数据 + * @param mixed $data 要处理的数据 * @return mixed */ protected function output($data) { + if (is_string($data)) { + if (0 !== strpos($data, 'options['encoding']; + $xml = ""; + $data = $xml . $data; + } + return $data; + } + // XML数据转换 return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); } /** * XML编码 - * @param mixed $data 数据 - * @param string $root 根节点名 - * @param string $item 数字索引的子节点名 - * @param string $attr 根节点属性 - * @param string $id 数字索引子节点key转换的属性名 - * @param string $encoding 数据编码 + * @access protected + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 * @return string */ protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) @@ -64,20 +74,23 @@ class Xml extends Response } $attr = implode(' ', $array); } + $attr = trim($attr); $attr = empty($attr) ? '' : " {$attr}"; $xml = ""; $xml .= "<{$root}{$attr}>"; $xml .= $this->dataToXml($data, $item, $id); $xml .= ""; + return $xml; } /** * 数据XML编码 - * @param mixed $data 数据 - * @param string $item 数字索引时的节点名称 - * @param string $id 数字索引key转换为的属性名 + * @access protected + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 * @return string */ protected function dataToXml($data, $item, $id) @@ -97,6 +110,7 @@ class Xml extends Response $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; $xml .= ""; } + return $xml; } } diff --git a/Server/thinkphp/library/think/session/driver/Memcache.php b/Server/thinkphp/library/think/session/driver/Memcache.php index 877d7bd7..40d7bb82 100644 --- a/Server/thinkphp/library/think/session/driver/Memcache.php +++ b/Server/thinkphp/library/think/session/driver/Memcache.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Memcache extends SessionHandler +class Memcache implements SessionHandlerInterface { protected $handler = null; protected $config = [ @@ -34,8 +34,8 @@ class Memcache extends SessionHandler /** * 打开Session * @access public - * @param string $savePath - * @param mixed $sessName + * @param string $savePath + * @param mixed $sessName */ public function open($savePath, $sessName) { @@ -43,13 +43,17 @@ class Memcache extends SessionHandler if (!extension_loaded('memcache')) { throw new Exception('not support:memcache'); } + $this->handler = new \Memcache; + // 支持集群 $hosts = explode(',', $this->config['host']); $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 foreach ((array) $hosts as $i => $host) { $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; @@ -57,6 +61,7 @@ class Memcache extends SessionHandler $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : $this->handler->addServer($host, $port, $this->config['persistent'], 1); } + return true; } @@ -69,13 +74,14 @@ class Memcache extends SessionHandler $this->gc(ini_get('session.gc_maxlifetime')); $this->handler->close(); $this->handler = null; + return true; } /** * 读取Session * @access public - * @param string $sessID + * @param string $sessID */ public function read($sessID) { @@ -85,8 +91,8 @@ class Memcache extends SessionHandler /** * 写入Session * @access public - * @param string $sessID - * @param String $sessData + * @param string $sessID + * @param string $sessData * @return bool */ public function write($sessID, $sessData) @@ -97,7 +103,7 @@ class Memcache extends SessionHandler /** * 删除Session * @access public - * @param string $sessID + * @param string $sessID * @return bool */ public function destroy($sessID) @@ -108,7 +114,7 @@ class Memcache extends SessionHandler /** * Session 垃圾回收 * @access public - * @param string $sessMaxLifeTime + * @param string $sessMaxLifeTime * @return true */ public function gc($sessMaxLifeTime) diff --git a/Server/thinkphp/library/think/session/driver/Memcached.php b/Server/thinkphp/library/think/session/driver/Memcached.php index 2994a07c..074b2ff7 100644 --- a/Server/thinkphp/library/think/session/driver/Memcached.php +++ b/Server/thinkphp/library/think/session/driver/Memcached.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Memcached extends SessionHandler +class Memcached implements SessionHandlerInterface { protected $handler = null; protected $config = [ @@ -35,8 +35,8 @@ class Memcached extends SessionHandler /** * 打开Session * @access public - * @param string $savePath - * @param mixed $sessName + * @param string $savePath + * @param mixed $sessName */ public function open($savePath, $sessName) { @@ -44,27 +44,35 @@ class Memcached extends SessionHandler if (!extension_loaded('memcached')) { throw new Exception('not support:memcached'); } + $this->handler = new \Memcached; + // 设置连接超时时间(单位:毫秒) if ($this->config['timeout'] > 0) { $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); } + // 支持集群 $hosts = explode(',', $this->config['host']); $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { $ports[0] = 11211; } + // 建立连接 $servers = []; foreach ((array) $hosts as $i => $host) { $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; } + $this->handler->addServers($servers); + if ('' != $this->config['username']) { $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); } + return true; } @@ -77,13 +85,14 @@ class Memcached extends SessionHandler $this->gc(ini_get('session.gc_maxlifetime')); $this->handler->quit(); $this->handler = null; + return true; } /** * 读取Session * @access public - * @param string $sessID + * @param string $sessID */ public function read($sessID) { @@ -93,8 +102,8 @@ class Memcached extends SessionHandler /** * 写入Session * @access public - * @param string $sessID - * @param String $sessData + * @param string $sessID + * @param string $sessData * @return bool */ public function write($sessID, $sessData) @@ -105,7 +114,7 @@ class Memcached extends SessionHandler /** * 删除Session * @access public - * @param string $sessID + * @param string $sessID * @return bool */ public function destroy($sessID) @@ -116,7 +125,7 @@ class Memcached extends SessionHandler /** * Session 垃圾回收 * @access public - * @param string $sessMaxLifeTime + * @param string $sessMaxLifeTime * @return true */ public function gc($sessMaxLifeTime) diff --git a/Server/thinkphp/library/think/session/driver/Redis.php b/Server/thinkphp/library/think/session/driver/Redis.php index 8d05126b..5a0e7bc7 100644 --- a/Server/thinkphp/library/think/session/driver/Redis.php +++ b/Server/thinkphp/library/think/session/driver/Redis.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Redis extends SessionHandler +class Redis implements SessionHandlerInterface { /** @var \Redis */ protected $handler = null; @@ -37,29 +37,38 @@ class Redis extends SessionHandler /** * 打开Session * @access public - * @param string $savePath - * @param mixed $sessName + * @param string $savePath + * @param mixed $sessName * @return bool * @throws Exception */ public function open($savePath, $sessName) { - // 检测php环境 - if (!extension_loaded('redis')) { - throw new Exception('not support:redis'); - } - $this->handler = new \Redis; + if (extension_loaded('redis')) { + $this->handler = new \Redis; - // 建立连接 - $func = $this->config['persistent'] ? 'pconnect' : 'connect'; - $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); - if ('' != $this->config['password']) { - $this->handler->auth($this->config['password']); - } + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } - if (0 != $this->config['select']) { - $this->handler->select($this->config['select']); + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->config as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication'])) { + $params[$key] = $val; + unset($this->config[$key]); + } + } + $this->handler = new \Predis\Client($this->config, $params); + } else { + throw new \BadFunctionCallException('not support: redis'); } return true; @@ -74,13 +83,14 @@ class Redis extends SessionHandler $this->gc(ini_get('session.gc_maxlifetime')); $this->handler->close(); $this->handler = null; + return true; } /** * 读取Session * @access public - * @param string $sessID + * @param string $sessID * @return string */ public function read($sessID) @@ -91,38 +101,79 @@ class Redis extends SessionHandler /** * 写入Session * @access public - * @param string $sessID - * @param String $sessData + * @param string $sessID + * @param string $sessData * @return bool */ public function write($sessID, $sessData) { if ($this->config['expire'] > 0) { - return $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + $result = $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); } else { - return $this->handler->set($this->config['session_name'] . $sessID, $sessData); + $result = $this->handler->set($this->config['session_name'] . $sessID, $sessData); } + + return $result ? true : false; } /** * 删除Session * @access public - * @param string $sessID + * @param string $sessID * @return bool */ public function destroy($sessID) { - return $this->handler->delete($this->config['session_name'] . $sessID) > 0; + return $this->handler->del($this->config['session_name'] . $sessID) > 0; } /** * Session 垃圾回收 * @access public - * @param string $sessMaxLifeTime + * @param string $sessMaxLifeTime * @return bool */ public function gc($sessMaxLifeTime) { return true; } + + /** + * Redis Session 驱动的加锁机制 + * @access public + * @param string $sessID 用于加锁的sessID + * @param integer $timeout 默认过期时间 + * @return bool + */ + public function lock($sessID, $timeout = 10) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $lockKey = 'LOCK_PREFIX_' . $sessID; + // 使用setnx操作加锁 + $isLock = $this->handler->setnx($lockKey, 1); + if ($isLock) { + // 设置过期时间,防止死任务的出现 + $this->handler->expire($lockKey, $timeout); + return true; + } + + return false; + } + + /** + * Redis Session 驱动的解锁机制 + * @access public + * @param string $sessID 用于解锁的sessID + */ + public function unlock($sessID) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $this->handler->del('LOCK_PREFIX_' . $sessID); + } } diff --git a/Server/thinkphp/library/think/template/TagLib.php b/Server/thinkphp/library/think/template/TagLib.php index c5b72f91..bbbb2c03 100644 --- a/Server/thinkphp/library/think/template/TagLib.php +++ b/Server/thinkphp/library/think/template/TagLib.php @@ -68,9 +68,9 @@ class TagLib protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; /** - * 构造函数 + * 架构函数 * @access public - * @param \stdClass $template 模板引擎对象 + * @param \stdClass $template 模板引擎对象 */ public function __construct($template) { @@ -88,6 +88,7 @@ class TagLib { $tags = []; $lib = $lib ? strtolower($lib) . ':' : ''; + foreach ($this->tags as $name => $val) { $close = !isset($val['close']) || $val['close'] ? 1 : 0; $tags[$close][$lib . $name] = $name; @@ -136,11 +137,14 @@ class TagLib // 对应的标签名 $name = $tags[1][$node['name']]; $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + // 解析标签属性 $attrs = $this->parseAttr($node['begin'][0], $name, $alias); $method = 'tag' . $name; + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 $replace = explode($break, $this->$method($attrs, $break)); + if (count($replace) > 1) { while ($beginArray) { $begin = end($beginArray); @@ -160,6 +164,7 @@ class TagLib $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; } } + while ($beginArray) { $begin = array_pop($beginArray); // 替换标签头部 @@ -180,12 +185,13 @@ class TagLib return $this->$method($attrs, ''); }, $content); } + return; } /** * 按标签生成正则 - * @access private + * @access public * @param array|string $tags 标签名 * @param boolean $close 是否为闭合标签 * @return string @@ -196,6 +202,7 @@ class TagLib $end = $this->tpl->config('taglib_end'); $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; $tagName = is_array($tags) ? implode('|', $tags) : $tags; + if ($single) { if ($close) { // 如果是闭合标签 @@ -211,25 +218,28 @@ class TagLib $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; } } + return '/' . $regex . '/is'; } /** * 分析标签属性 正则方式 * @access public - * @param string $str 标签属性字符串 - * @param string $name 标签名 - * @param string $alias 别名 + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 * @return array */ public function parseAttr($str, $name, $alias = '') { $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; $result = []; + if (preg_match_all($regex, $str, $matches)) { foreach ($matches['name'] as $key => $val) { $result[$val] = $matches['value'][$key]; } + if (!isset($this->tags[$name])) { // 检测是否存在别名定义 foreach ($this->tags as $key => $val) { @@ -251,6 +261,7 @@ class TagLib $result[$type] = $alias; } } + if (!empty($tag['must'])) { $must = explode(',', $tag['must']); foreach ($must as $name) { @@ -275,6 +286,7 @@ class TagLib throw new Exception('tag error:' . $name); } } + return $result; } @@ -286,11 +298,13 @@ class TagLib */ public function parseCondition($condition) { - if (strpos($condition, ':')) { + if (!strpos($condition, '::') && strpos($condition, ':')) { $condition = ' ' . substr(strstr($condition, ':'), 1); } + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); $this->tpl->parseVar($condition); + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 return $condition; } @@ -298,12 +312,13 @@ class TagLib /** * 自动识别构建变量 * @access public - * @param string $name 变量描述 + * @param string $name 变量描述 * @return string */ public function autoBuildVar(&$name) { $flag = substr($name, 0, 1); + if (':' == $flag) { // 以:开头为函数调用,解析前去掉: $name = substr($name, 1); @@ -313,11 +328,14 @@ class TagLib if (defined($name)) { return $name; } + // 不以$开头并且也不是常量,自动补上$前缀 $name = '$' . $name; } + $this->tpl->parseVar($name); - $this->tpl->parseVarFunction($name); + $this->tpl->parseVarFunction($name, false); + return $name; } @@ -326,7 +344,6 @@ class TagLib * @access public * @return array */ - // 获取标签定义 public function getTags() { return $this->tags; diff --git a/Server/thinkphp/library/think/template/driver/File.php b/Server/thinkphp/library/think/template/driver/File.php index a9a86bf2..3b96a0f3 100644 --- a/Server/thinkphp/library/think/template/driver/File.php +++ b/Server/thinkphp/library/think/template/driver/File.php @@ -19,17 +19,20 @@ class File /** * 写入编译缓存 - * @param string $cacheFile 缓存的文件名 - * @param string $content 缓存的内容 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 * @return void|array */ public function write($cacheFile, $content) { // 检测模板目录 $dir = dirname($cacheFile); + if (!is_dir($dir)) { mkdir($dir, 0755, true); } + // 生成模板缓存文件 if (false === file_put_contents($cacheFile, $content)) { throw new Exception('cache write error:' . $cacheFile, 11602); @@ -38,25 +41,29 @@ class File /** * 读取编译编译 - * @param string $cacheFile 缓存的文件名 - * @param array $vars 变量数组 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 * @return void */ public function read($cacheFile, $vars = []) { $this->cacheFile = $cacheFile; + if (!empty($vars) && is_array($vars)) { // 模板阵列变量分解成为独立变量 extract($vars, EXTR_OVERWRITE); } + //载入模版缓存文件 include $this->cacheFile; } /** * 检查编译缓存是否有效 - * @param string $cacheFile 缓存的文件名 - * @param int $cacheTime 缓存时间 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 * @return boolean */ public function check($cacheFile, $cacheTime) @@ -65,10 +72,12 @@ class File if (!file_exists($cacheFile)) { return false; } - if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { + + if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) { // 缓存是否在有效期 return false; } + return true; } } diff --git a/Server/thinkphp/library/think/template/taglib/Cx.php b/Server/thinkphp/library/think/template/taglib/Cx.php index 31e0698d..ad741f28 100644 --- a/Server/thinkphp/library/think/template/taglib/Cx.php +++ b/Server/thinkphp/library/think/template/taglib/Cx.php @@ -56,8 +56,8 @@ class Cx extends Taglib * 格式: * {php}echo $name{/php} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagPhp($tag, $content) @@ -74,8 +74,8 @@ class Cx extends Taglib * {user.email} * {/volist} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string|void */ public function tagVolist($tag, $content) @@ -90,6 +90,7 @@ class Cx extends Taglib // 允许使用函数设定数据集 {$vo.name} $parseStr = 'autoBuildVar($name); $parseStr .= '$_result=' . $name . ';'; @@ -99,12 +100,14 @@ class Cx extends Taglib } $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; } else { $parseStr .= ' $__LIST__ = ' . $name . ';'; } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; $parseStr .= 'else: '; $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; @@ -116,6 +119,7 @@ class Cx extends Taglib if (!empty($parseStr)) { return $parseStr; } + return; } @@ -126,8 +130,8 @@ class Cx extends Taglib * {user.username} * {/foreach} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string|void */ public function tagForeach($tag, $content) @@ -141,6 +145,7 @@ class Cx extends Taglib $parseStr .= ''; return $parseStr; } + $name = $tag['name']; $key = !empty($tag['key']) ? $tag['key'] : 'key'; $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; @@ -149,6 +154,7 @@ class Cx extends Taglib $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; $parseStr = 'autoBuildVar($name); } + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { if (!isset($var)) { @@ -177,7 +185,9 @@ class Cx extends Taglib $index = $tag['index']; $parseStr .= '$' . $index . '=0; '; } + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + // 设置了索引项 if (isset($tag['index'])) { $index = $tag['index']; @@ -187,6 +197,7 @@ class Cx extends Taglib } $parseStr .= '++$' . $index . '; '; } + $parseStr .= '?>'; // 循环体中的内容 $parseStr .= $content; @@ -195,6 +206,7 @@ class Cx extends Taglib if (!empty($parseStr)) { return $parseStr; } + return; } @@ -207,8 +219,8 @@ class Cx extends Taglib * {/if} * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagIf($tag, $content) @@ -216,6 +228,7 @@ class Cx extends Taglib $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; $condition = $this->parseCondition($condition); $parseStr = '' . $content . ''; + return $parseStr; } @@ -223,8 +236,8 @@ class Cx extends Taglib * elseif标签解析 * 格式:见if标签 * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagElseif($tag, $content) @@ -232,6 +245,7 @@ class Cx extends Taglib $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; $condition = $this->parseCondition($condition); $parseStr = ''; + return $parseStr; } @@ -239,12 +253,13 @@ class Cx extends Taglib * else标签解析 * 格式:见if标签 * @access public - * @param array $tag 标签属性 + * @param array $tag 标签属性 * @return string */ public function tagElse($tag) { $parseStr = ''; + return $parseStr; } @@ -257,8 +272,8 @@ class Cx extends Taglib * {default /}other * {/switch} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagSwitch($tag, $content) @@ -266,20 +281,22 @@ class Cx extends Taglib $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; $name = $this->autoBuildVar($name); $parseStr = '' . $content . ''; + return $parseStr; } /** * case标签解析 需要配合switch才有效 * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagCase($tag, $content) { $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($value); $value = 'case ' . $value . ':'; @@ -292,11 +309,14 @@ class Cx extends Taglib } else { $value = 'case "' . $value . '":'; } + $parseStr = '' . $content; $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if ('' == $isBreak || $isBreak) { $parseStr .= ''; } + return $parseStr; } @@ -304,13 +324,14 @@ class Cx extends Taglib * default标签解析 需要配合switch才有效 * 使用: {default /}ddfdf * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagDefault($tag) { $parseStr = ''; + return $parseStr; } @@ -319,8 +340,8 @@ class Cx extends Taglib * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq * 格式: {compare name="" type="eq" value="" }content{/compare} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagCompare($tag, $content) @@ -330,11 +351,13 @@ class Cx extends Taglib $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 $name = $this->autoBuildVar($name); $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($value); } else { $value = '\'' . $value . '\''; } + switch ($type) { case 'equal': $type = 'eq'; @@ -345,6 +368,7 @@ class Cx extends Taglib } $type = $this->parseCondition(' ' . $type . ' '); $parseStr = '' . $content . ''; + return $parseStr; } @@ -354,8 +378,8 @@ class Cx extends Taglib * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} * example: {range name="a" value="1,2,3" type='in' }content{/range} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagRange($tag, $content) @@ -366,6 +390,7 @@ class Cx extends Taglib $name = $this->autoBuildVar($name); $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($value); $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; @@ -373,6 +398,7 @@ class Cx extends Taglib $value = '"' . $value . '"'; $str = 'explode(\',\',' . $value . ')'; } + if ('between' == $type) { $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; } elseif ('notbetween' == $type) { @@ -381,6 +407,7 @@ class Cx extends Taglib $fun = ('in' == $type) ? 'in_array' : '!in_array'; $parseStr = '' . $content . ''; } + return $parseStr; } @@ -389,8 +416,8 @@ class Cx extends Taglib * 如果某个变量已经设置 则输出内容 * 格式: {present name="" }content{/present} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagPresent($tag, $content) @@ -398,6 +425,7 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = '' . $content . ''; + return $parseStr; } @@ -406,8 +434,8 @@ class Cx extends Taglib * 如果某个变量没有设置,则输出内容 * 格式: {notpresent name="" }content{/notpresent} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagNotpresent($tag, $content) @@ -415,6 +443,7 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = '' . $content . ''; + return $parseStr; } @@ -423,8 +452,8 @@ class Cx extends Taglib * 如果某个变量为empty 则输出内容 * 格式: {empty name="" }content{/empty} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagEmpty($tag, $content) @@ -432,6 +461,7 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = 'isEmpty())): ?>' . $content . ''; + return $parseStr; } @@ -440,8 +470,8 @@ class Cx extends Taglib * 如果某个变量不为empty 则输出内容 * 格式: {notempty name="" }content{/notempty} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagNotempty($tag, $content) @@ -449,34 +479,39 @@ class Cx extends Taglib $name = $tag['name']; $name = $this->autoBuildVar($name); $parseStr = 'isEmpty()))): ?>' . $content . ''; + return $parseStr; } /** * 判断是否已经定义了该常量 * {defined name='TXT'}已定义{/defined} - * @param array $tag - * @param string $content + * @access public + * @param array $tag + * @param string $content * @return string */ public function tagDefined($tag, $content) { $name = $tag['name']; $parseStr = '' . $content . ''; + return $parseStr; } /** * 判断是否没有定义了该常量 * {notdefined name='TXT'}已定义{/notdefined} - * @param array $tag - * @param string $content + * @access public + * @param array $tag + * @param string $content * @return string */ public function tagNotdefined($tag, $content) { $name = $tag['name']; $parseStr = '' . $content . ''; + return $parseStr; } @@ -484,16 +519,18 @@ class Cx extends Taglib * load 标签解析 {load file="/static/js/base.js" /} * 格式:{load file="/static/css/base.css" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagLoad($tag, $content) { - $file = isset($tag['file']) ? $tag['file'] : $tag['href']; - $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + $parseStr = ''; $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) if (isset($tag['value'])) { $name = $tag['value']; @@ -505,6 +542,7 @@ class Cx extends Taglib // 文件方式导入 $array = explode(',', $file); + foreach ($array as $val) { $type = strtolower(substr(strrchr($val, '.'), 1)); switch ($type) { @@ -519,6 +557,7 @@ class Cx extends Taglib break; } } + return $parseStr . $endStr; } @@ -527,20 +566,23 @@ class Cx extends Taglib * 在模板中给某个变量赋值 支持变量赋值 * 格式: {assign name="" value="" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagAssign($tag, $content) { $name = $this->autoBuildVar($tag['name']); $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($tag['value']); } else { $value = '\'' . $tag['value'] . '\''; } + $parseStr = ''; + return $parseStr; } @@ -549,20 +591,23 @@ class Cx extends Taglib * 在模板中定义常量 支持变量赋值 * 格式: {define name="" value="" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagDefine($tag, $content) { $name = '\'' . $tag['name'] . '\''; $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($tag['value']); } else { $value = '\'' . $tag['value'] . '\''; } + $parseStr = ''; + return $parseStr; } @@ -573,8 +618,8 @@ class Cx extends Taglib * content * {/for} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagFor($tag, $content) @@ -586,6 +631,7 @@ class Cx extends Taglib $comparison = 'lt'; $name = 'i'; $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 foreach ($tag as $key => $value) { $value = trim($value); @@ -617,6 +663,7 @@ class Cx extends Taglib $parseStr .= 'for($' . $name . '=$__FOR_START_' . $rand . '__;' . $this->parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; $parseStr .= $content; $parseStr .= ''; + return $parseStr; } @@ -624,8 +671,8 @@ class Cx extends Taglib * url函数的tag标签 * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagUrl($tag, $content) @@ -634,6 +681,7 @@ class Cx extends Taglib $vars = isset($tag['vars']) ? $tag['vars'] : ''; $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + return ''; } @@ -650,8 +698,8 @@ class Cx extends Taglib * {/if} * {/function} * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param array $tag 标签属性 + * @param string $content 标签内容 * @return string */ public function tagFunction($tag, $content) @@ -660,14 +708,17 @@ class Cx extends Taglib $vars = !empty($tag['vars']) ? $tag['vars'] : ''; $call = !empty($tag['call']) ? $tag['call'] : ''; $use = ['&$' . $name]; + if (!empty($tag['use'])) { foreach (explode(',', $tag['use']) as $val) { $use[] = '&' . ltrim(trim($val), '&'); } } + $parseStr = '' . $content . '' : '?>'; + return $parseStr; } } diff --git a/Server/thinkphp/library/think/view/driver/Php.php b/Server/thinkphp/library/think/view/driver/Php.php index f594a438..7948dc05 100644 --- a/Server/thinkphp/library/think/view/driver/Php.php +++ b/Server/thinkphp/library/think/view/driver/Php.php @@ -14,13 +14,13 @@ namespace think\view\driver; use think\App; use think\exception\TemplateNotFoundException; use think\Loader; -use think\Log; -use think\Request; class Php { // 模板引擎参数 protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, // 视图基础目录(集中式) 'view_base' => '', // 模板起始路径 @@ -28,22 +28,23 @@ class Php // 模板文件后缀 'view_suffix' => 'php', // 模板文件名分隔符 - 'view_depr' => DS, - // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 - 'auto_rule' => 1, + 'view_depr' => DIRECTORY_SEPARATOR, ]; + protected $template; + protected $app; protected $content; - public function __construct($config = []) + public function __construct(App $app, $config = []) { - $this->config = array_merge($this->config, $config); + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); } /** * 检测是否存在模板文件 * @access public - * @param string $template 模板文件或者模板规则 + * @param string $template 模板文件或者模板规则 * @return bool */ public function exists($template) @@ -52,14 +53,15 @@ class Php // 获取模板文件名 $template = $this->parseTemplate($template); } + return is_file($template); } /** * 渲染模板文件 * @access public - * @param string $template 模板文件 - * @param array $data 模板变量 + * @param string $template 模板文件 + * @param array $data 模板变量 * @return void */ public function fetch($template, $data = []) @@ -68,13 +70,17 @@ class Php // 获取模板文件名 $template = $this->parseTemplate($template); } + // 模板不存在 抛出异常 if (!is_file($template)) { throw new TemplateNotFoundException('template not exists:' . $template, $template); } + $this->template = $template; + // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + $this->app + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); extract($data, EXTR_OVERWRITE); include $this->template; @@ -83,8 +89,8 @@ class Php /** * 渲染模板内容 * @access public - * @param string $content 模板内容 - * @param array $data 模板变量 + * @param string $content 模板内容 + * @param array $data 模板变量 * @return void */ public function display($content, $data = []) @@ -98,52 +104,65 @@ class Php /** * 自动定位模板文件 * @access private - * @param string $template 模板文件规则 + * @param string $template 模板文件规则 * @return string */ private function parseTemplate($template) { if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; + $this->config['view_path'] = $this->app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } - $request = Request::instance(); + $request = $this->app['request']; + // 获取视图根目录 if (strpos($template, '@')) { // 跨模块调用 list($module, $template) = explode('@', $template); } + if ($this->config['view_base']) { // 基础视图目录 $module = isset($module) ? $module : $request->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $depr, $template); $controller = Loader::parseName($request->controller()); + if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } } } else { $template = str_replace(['/', ':'], $depr, substr($template, 1)); } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + /** * 配置模板引擎 * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 + * @param string|array $name 参数名 + * @param mixed $value 参数值 * @return void */ public function config($name, $value = null) @@ -157,4 +176,8 @@ class Php } } + public function __debugInfo() + { + return ['config' => $this->config]; + } } diff --git a/Server/thinkphp/library/think/view/driver/Think.php b/Server/thinkphp/library/think/view/driver/Think.php index a314ad60..877aee85 100644 --- a/Server/thinkphp/library/think/view/driver/Think.php +++ b/Server/thinkphp/library/think/view/driver/Think.php @@ -14,16 +14,18 @@ namespace think\view\driver; use think\App; use think\exception\TemplateNotFoundException; use think\Loader; -use think\Log; -use think\Request; use think\Template; class Think { // 模板引擎实例 private $template; + private $app; + // 模板引擎参数 protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, // 视图基础目录(集中式) 'view_base' => '', // 模板起始路径 @@ -31,27 +33,27 @@ class Think // 模板文件后缀 'view_suffix' => 'html', // 模板文件名分隔符 - 'view_depr' => DS, + 'view_depr' => DIRECTORY_SEPARATOR, // 是否开启模板编译缓存,设为false则每次都会重新编译 'tpl_cache' => true, - // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 - 'auto_rule' => 1, ]; - public function __construct($config = []) + public function __construct(App $app, $config = []) { - $this->config = array_merge($this->config, $config); + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; + $this->config['view_path'] = $app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } - $this->template = new Template($this->config); + $this->template = new Template($app, $this->config); } /** * 检测是否存在模板文件 * @access public - * @param string $template 模板文件或者模板规则 + * @param string $template 模板文件或者模板规则 * @return bool */ public function exists($template) @@ -60,15 +62,16 @@ class Think // 获取模板文件名 $template = $this->parseTemplate($template); } + return is_file($template); } /** * 渲染模板文件 * @access public - * @param string $template 模板文件 - * @param array $data 模板变量 - * @param array $config 模板参数 + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 * @return void */ public function fetch($template, $data = [], $config = []) @@ -77,21 +80,25 @@ class Think // 获取模板文件名 $template = $this->parseTemplate($template); } + // 模板不存在 抛出异常 if (!is_file($template)) { throw new TemplateNotFoundException('template not exists:' . $template, $template); } + // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + $this->app + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + $this->template->fetch($template, $data, $config); } /** * 渲染模板内容 * @access public - * @param string $template 模板内容 - * @param array $data 模板变量 - * @param array $config 模板参数 + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 * @return void */ public function display($template, $data = [], $config = []) @@ -102,49 +109,62 @@ class Think /** * 自动定位模板文件 * @access private - * @param string $template 模板文件规则 + * @param string $template 模板文件规则 * @return string */ private function parseTemplate($template) { // 分析模板文件规则 - $request = Request::instance(); + $request = $this->app['request']; + // 获取视图根目录 if (strpos($template, '@')) { // 跨模块调用 list($module, $template) = explode('@', $template); } + if ($this->config['view_base']) { // 基础视图目录 $module = isset($module) ? $module : $request->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $depr, $template); $controller = Loader::parseName($request->controller()); + if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } } } else { $template = str_replace(['/', ':'], $depr, substr($template, 1)); } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + /** * 配置或者获取模板引擎参数 * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 + * @param string|array $name 参数名 + * @param mixed $value 参数值 * @return mixed */ public function config($name, $value = null) @@ -164,4 +184,9 @@ class Think { return call_user_func_array([$this->template, $method], $params); } + + public function __debugInfo() + { + return ['config' => $this->config]; + } } diff --git a/Server/thinkphp/library/traits/controller/Jump.php b/Server/thinkphp/library/traits/controller/Jump.php index 6a572246..41f7e930 100644 --- a/Server/thinkphp/library/traits/controller/Jump.php +++ b/Server/thinkphp/library/traits/controller/Jump.php @@ -1,7 +1,7 @@ server('HTTP_REFERER'))) { - $url = Request::instance()->server('HTTP_REFERER'); - } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { - $url = Url::build($url); + if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { + $url = $_SERVER["HTTP_REFERER"]; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); } - $type = $this->getResponseType(); $result = [ 'code' => 1, 'msg' => $msg, @@ -51,15 +52,13 @@ trait Jump 'wait' => $wait, ]; + $type = $this->getResponseType(); + // 把跳转模板的渲染下沉,这样在 response_send 行为里通过getData()获得的数据是一致性的格式 if ('html' == strtolower($type)) { - $template = Config::get('template'); - $view = Config::get('view_replace_str'); - - $result = ViewTemplate::instance($template, $view) - ->fetch(Config::get('dispatch_success_tmpl'), $result); + $type = 'jump'; } - $response = Response::create($result, $type)->header($header); + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_success_tmpl')]); throw new HttpResponseException($response); } @@ -67,23 +66,22 @@ trait Jump /** * 操作错误跳转的快捷方法 * @access protected - * @param mixed $msg 提示信息 - * @param string $url 跳转的 URL 地址 - * @param mixed $data 返回的数据 - * @param int $wait 跳转等待时间 - * @param array $header 发送的 Header 信息 + * @param mixed $msg 提示信息 + * @param string $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @param array $header 发送的Header信息 * @return void - * @throws HttpResponseException */ protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) { + $type = $this->getResponseType(); if (is_null($url)) { - $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; - } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { - $url = Url::build($url); + $url = $this->app['request']->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app['url']->build($url); } - $type = $this->getResponseType(); $result = [ 'code' => 0, 'msg' => $msg, @@ -93,37 +91,33 @@ trait Jump ]; if ('html' == strtolower($type)) { - $template = Config::get('template'); - $view = Config::get('view_replace_str'); - - $result = ViewTemplate::instance($template, $view) - ->fetch(Config::get('dispatch_error_tmpl'), $result); + $type = 'jump'; } - $response = Response::create($result, $type)->header($header); + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_error_tmpl')]); throw new HttpResponseException($response); } /** - * 返回封装后的 API 数据到客户端 + * 返回封装后的API数据到客户端 * @access protected - * @param mixed $data 要返回的数据 - * @param int $code 返回的 code - * @param mixed $msg 提示信息 - * @param string $type 返回数据格式 - * @param array $header 发送的 Header 信息 + * @param mixed $data 要返回的数据 + * @param integer $code 返回的code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的Header信息 * @return void - * @throws HttpResponseException */ protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) { $result = [ 'code' => $code, 'msg' => $msg, - 'time' => Request::instance()->server('REQUEST_TIME'), + 'time' => time(), 'data' => $data, ]; + $type = $type ?: $this->getResponseType(); $response = Response::create($result, $type)->header($header); @@ -131,37 +125,44 @@ trait Jump } /** - * URL 重定向 + * URL重定向 * @access protected - * @param string $url 跳转的 URL 表达式 - * @param array|int $params 其它 URL 参数 - * @param int $code http code - * @param array $with 隐式传参 + * @param string $url 跳转的URL表达式 + * @param array|integer $params 其它URL参数 + * @param integer $code http code + * @param array $with 隐式传参 * @return void - * @throws HttpResponseException */ protected function redirect($url, $params = [], $code = 302, $with = []) { + $response = new Redirect($url); + if (is_integer($params)) { $code = $params; $params = []; } - $response = new Redirect($url); $response->code($code)->params($params)->with($with); throw new HttpResponseException($response); } /** - * 获取当前的 response 输出类型 + * 获取当前的response 输出类型 * @access protected * @return string */ protected function getResponseType() { - return Request::instance()->isAjax() - ? Config::get('default_ajax_return') - : Config::get('default_return_type'); + if (!$this->app) { + $this->app = Container::get('app'); + } + + $isAjax = $this->app['request']->isAjax(); + $config = $this->app['config']; + + return $isAjax + ? $config->get('default_ajax_return') + : $config->get('default_return_type'); } } diff --git a/Server/thinkphp/tpl/default_index.tpl b/Server/thinkphp/tpl/default_index.tpl index 8538b4df..e5c1363a 100644 --- a/Server/thinkphp/tpl/default_index.tpl +++ b/Server/thinkphp/tpl/default_index.tpl @@ -5,6 +5,6 @@ class Index{$suffix} { public function index() { - return '

    :)

    ThinkPHP V5
    十年磨一剑 - 为API开发设计的高性能框架

    [ V5.0 版本由 七牛云 独家赞助发布 ]
    '; + return '

    :)

    ThinkPHP V5.1
    12载初心不改(2006-2018) - 你值得信赖的PHP框架

    '; } } diff --git a/Server/thinkphp/tpl/page_trace.tpl b/Server/thinkphp/tpl/page_trace.tpl index 7c5df6fb..2e5afbab 100644 --- a/Server/thinkphp/tpl/page_trace.tpl +++ b/Server/thinkphp/tpl/page_trace.tpl @@ -24,7 +24,7 @@
    -
    +
    getUseTime().'s ';?>
    diff --git a/Server/thinkphp/tpl/think_exception.tpl b/Server/thinkphp/tpl/think_exception.tpl index 21bbafc3..c8988859 100644 --- a/Server/thinkphp/tpl/think_exception.tpl +++ b/Server/thinkphp/tpl/think_exception.tpl @@ -1,537 +1,508 @@ -'.end($names).''; - } - } - - if(!function_exists('parse_file')){ - function parse_file($file, $line) - { - return ''.basename($file)." line {$line}".''; - } - } - - if(!function_exists('parse_args')){ - function parse_args($args) - { - $result = []; - - foreach ($args as $key => $item) { - switch (true) { - case is_object($item): - $value = sprintf('object(%s)', parse_class(get_class($item))); - break; - case is_array($item): - if(count($item) > 3){ - $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); - } else { - $value = sprintf('[%s]', parse_args($item)); - } - break; - case is_string($item): - if(strlen($item) > 20){ - $value = sprintf( - '\'%s...\'', - htmlentities($item), - htmlentities(substr($item, 0, 20)) - ); - } else { - $value = sprintf("'%s'", htmlentities($item)); - } - break; - case is_int($item): - case is_float($item): - $value = $item; - break; - case is_null($item): - $value = 'null'; - break; - case is_bool($item): - $value = '' . ($item ? 'true' : 'false') . ''; - break; - case is_resource($item): - $value = 'resource'; - break; - default: - $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); - break; - } - - $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; - } - - return implode(', ', $result); - } - } -?> - - - - - <?php echo \think\Lang::get('System Error'); ?> - - - - - -
    - -
    - -
    -
    - -
    -
    -

    [

    -
    -

    -
    - -
    - -
    -
      $value) { ?>
    -
    - -
    -

    Call Stack

    -
      -
    1. - -
    2. - -
    3. - -
    -
    -
    - -
    - -

    - -
    - - - -
    -

    Exception Datas

    - $value) { ?> - - - - - - - $val) { ?> - - - - - - - -
    empty
    - -
    - -
    - - - -
    -

    Environment Variables

    - $value) { ?> -
    - -
    -
    -
    empty
    -
    - -

    -
    - $val) { ?> -
    -
    -
    - -
    -
    - -
    - -
    - -
    - - - - - - - - +'.end($names).''; + } + } + + if(!function_exists('parse_file')){ + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } + } + + if(!function_exists('parse_args')){ + function parse_args($args) + { + $result = []; + + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%s)', parse_class(get_class($item))); + break; + case is_array($item): + if(count($item) > 3){ + $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); + } else { + $value = sprintf('[%s]', parse_args($item)); + } + break; + case is_string($item): + if(strlen($item) > 20){ + $value = sprintf( + '\'%s...\'', + htmlentities($item), + htmlentities(substr($item, 0, 20)) + ); + } else { + $value = sprintf("'%s'", htmlentities($item)); + } + break; + case is_int($item): + case is_float($item): + $value = $item; + break; + case is_null($item): + $value = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } + } +?> + + + + + 系统发生错误 + + + + + +
    + +
    + +
    +
    + +
    +
    +

    [

    +
    +

    +
    + +
    + +
    +
      $value) { ?>
    +
    + +
    +

    Call Stack

    +
      +
    1. + +
    2. + +
    3. + +
    +
    +
    + +
    + +

    + +
    + + + +
    +

    Exception Datas

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + +
    +

    Environment Variables

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + + + + + +