feat: 本次提交更新内容如下

php有问题,覆盖下
This commit is contained in:
笔记本里的永平
2025-07-07 14:52:56 +08:00
parent c29fbd1bbb
commit c9a3aaab58
338 changed files with 70315 additions and 12737 deletions

View File

@@ -1,4 +1,8 @@
/composer.lock
/vendor
.idea
composer.phar
composer.lock
.DS_Store
Thumbs.db
/phpunit.xml
/.idea
/.vscode

View File

@@ -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~7GitHub 会自动更新你的 `pull request`
10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。
*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`*

View File

@@ -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® 商标和著作权所有者为上海顶想信息科技有限公司。

View File

@@ -1,103 +1,88 @@
ThinkPHP 5.0
![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221)
ThinkPHP 5.1LTS —— 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)
## 版权信息

View File

@@ -8,58 +8,45 @@
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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,
]);

View File

@@ -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"
}
}
}

View File

@@ -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\\',
],
];

File diff suppressed because it is too large Load Diff

View File

@@ -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必须是浮点数',

File diff suppressed because it is too large Load Diff

View File

@@ -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 . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~',
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 . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~', 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) ? "<?php\n" : ''
);
if (!is_file($this->basePath . $file)) {
file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "<?php\n" : '');
}
}
}
@@ -95,60 +103,58 @@ class Build
/**
* 创建模块
* @access public
* @param string $module 模块名
* @param array $list build 列表
* @param string $module 模块名
* @param array $list build列表
* @param string $namespace 应用类库命名空间
* @param bool $suffix 类库后缀
* @param bool $suffix 类库后缀
* @return void
*/
public static function module($module = '', $list = [], $namespace = 'app', $suffix = false)
public function module($module = '', $list = [], $namespace = 'app', $suffix = false)
{
$module = $module ?: '';
$module = $module ? $module : '';
// 创建模块目录
!is_dir(APP_PATH . $module) && mkdir(APP_PATH . $module);
// 如果不是 runtime 目录则需要创建配置文件和公共文件、创建模块的默认页面
if (basename(RUNTIME_PATH) != $module) {
self::buildCommon($module);
self::buildHello($module, $namespace, $suffix);
if (!is_dir($this->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) ? "<?php\n" : ''
);
file_put_contents($modulePath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "<?php\n" : '');
}
}
} else {
// 生成相关 MVC 文件
// 生成相关MVC文件
foreach ($file as $val) {
$val = trim($val);
$filename = $modulePath . $path . DS . $val . ($suffix ? ucfirst($path) : '') . EXT;
$filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . ($suffix ? ucfirst($path) : '') . '.php';
$space = $namespace . '\\' . ($module ? $module . '\\' : '') . $path;
$class = $val . ($suffix ? ucfirst($path) : '');
switch ($path) {
case 'controller': // 控制器
$content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
@@ -157,8 +163,8 @@ class Build
$content = "<?php\nnamespace {$space};\n\nuse think\Model;\n\nclass {$class} extends Model\n{\n\n}";
break;
case 'view': // 视图
$filename = $modulePath . $path . DS . $val . '.html';
self::checkDirBuild(dirname($filename));
$filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . '.html';
$this->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 = '<?php ' . PHP_EOL . '//根据 Annotation 自动生成的路由规则';
if (!$layer) {
$layer = $this->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, "<?php\n//配置文件\nreturn [\n\n];");
if (!is_file($filename)) {
file_put_contents($filename, "<?php\n//配置文件\nreturn [\n\n];");
}
$common = APP_PATH . ($module ? $module . DS : '') . 'common.php';
if (!is_file($common)) file_put_contents($common, "<?php\n");
$filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php';
if (!is_file($filename)) {
file_put_contents($filename, "<?php\n");
}
}
/**
@@ -228,8 +406,10 @@ class Build
* @param string $dirname 目录名称
* @return void
*/
protected static function checkDirBuild($dirname)
protected function checkDirBuild($dirname)
{
!is_dir($dirname) && mkdir($dirname, 0755, true);
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
}
}

View File

@@ -13,82 +13,101 @@ namespace think;
use think\cache\Driver;
/**
* Class Cache
*
* @package think
*
* @mixin Driver
* @mixin \think\cache\driver\File
*/
class Cache
{
/**
* @var array 缓存实例
* 缓存实例
* @var array
*/
public static $instance = [];
protected $instance = [];
/**
* @var int 缓存读取次数
* 缓存配置
* @var array
*/
public static $readTimes = 0;
protected $config = [];
/**
* @var int 缓存写入次数
* 操作句柄
* @var object
*/
public static $writeTimes = 0;
protected $handler;
public function __construct(array $config = [])
{
$this->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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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(
'<info>%s</info> version <comment>%s</comment>',
$this->getName(),
$this->getVersion()
);
return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
}
return '<info>Console Tool</info>';
}
/**
* 注册一个指令
* 注册一个指令 (便于动态创建指令)
* @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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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&param2=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);
}
}

View File

@@ -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 = '<pre>' . $label . $output . '</pre>';
}
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, '</body>');
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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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()
{

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 <b>Iterator</b> or
* <b>Traversable</b>
*/
@@ -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(),
];
}
/**

View File

@@ -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)
{

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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 = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\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 <xiaobo.sun@qq.com>
* @return boolean

View File

@@ -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, ("<?php return " . var_export($value, true) . ";"));
// 通过设置修改时间实现有效期
if ($ret) {
isset($first) && $this->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'));
}
}

View File

@@ -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();
}
}

View File

@@ -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), ','));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}

View File

@@ -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("<info>Clear Successed</info>");
}
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);
}
}
}

View File

@@ -19,7 +19,6 @@ use think\console\Output;
class Help extends Command
{
private $command;
/**

View File

@@ -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'),
]);
}
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
{
//
}

View File

@@ -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('<info>Succeed!</info>');
}
@@ -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) !== '<?') {
$contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
if ($replacements === 0) {
if (0 === $replacements) {
return [];
}
}
@@ -279,9 +264,9 @@ EOF;
$namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\';
} else {
$name = $matches['name'][$i];
if ($name[0] === ':') {
if (':' === $name[0]) {
$name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
} elseif ($matches['type'][$i] === 'enum') {
} elseif ('enum' === $matches['type'][$i]) {
$name = rtrim($name, ':');
}
$classes[] = ltrim($namespace . $name, '\\');

View File

@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// | Copyright (c) 2006-2017 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
@@ -10,17 +10,15 @@
// +----------------------------------------------------------------------
namespace think\console\command\optimize;
use think\Config as ThinkConfig;
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
{
/** @var Output */
protected $output;
protected function configure()
{
$this->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 = '<?php ' . PHP_EOL . $this->buildCacheContent($module);
if (!is_dir(RUNTIME_PATH . $module)) {
@mkdir(RUNTIME_PATH . $module, 0755, true);
$content = '<?php ' . PHP_EOL . $this->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('<info>Succeed!</info>');
}
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;
}
}

View File

@@ -6,19 +6,17 @@
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// | 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('<info>Succeed!</info>');
}
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 = '<?php ' . PHP_EOL . 'return ';
$content .= var_export($rules, true) . ';';
$content = str_replace(['\'[__start__', '__end__]\''], '', stripcslashes($content));
$content .= var_export(Container::get('route')->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__]';
}
}
}

View File

@@ -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('<info>Succeed!</info>');
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('<info>Succeed!</info>');
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('<info>Succeed!</info>');
}
@@ -97,22 +95,24 @@ class Schema extends Command
$content = '<?php ' . PHP_EOL . 'return ';
$info = $class::getConnection()->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 = '<?php ' . PHP_EOL . 'return ';
$info = Db::connect($config)->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);
}
}
}

View File

@@ -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 = [])
{

View File

@@ -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;
}

View File

@@ -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]];
}

View File

@@ -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);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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()';
}

View File

@@ -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()';
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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'");
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 ;

View File

@@ -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)
{

View File

@@ -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 = [])
{

View File

@@ -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 = [])
{

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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()

View File

@@ -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'] : [];
}
}

View File

@@ -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)
{

View File

@@ -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;
}
/**

View File

@@ -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]';

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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 = '')
{

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
}
}

Some files were not shown because too many files have changed in this diff Show More